-
Notifications
You must be signed in to change notification settings - Fork 17
/
lyrics.lua
2991 lines (2790 loc) · 119 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.
obs = obslua
bit = require("bit")
-- source definitions
source_data = {}
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 sources
source_name = ""
alternate_source_name = ""
static_source_name = ""
static_text = ""
title_source_name = ""
-- settings
windows_os = false
first_open = true
display_lines = 0
ensure_lines = true
-- lyrics/alternate lyrics by page
lyrics = {}
alternate = {}
-- verse indicies if marked
verses = {}
page_index = 0 -- current page of lyrics being displayed
prepared_index = 0 -- 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
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
all_sources_fade = false -- Title and Static should only fade when lyrics are changing or during show/hide
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
source_active = false -- true when a lyric load source is active in the current scene (song is loaded or available to load)
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)
-- hotkeys
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
hotkey_n_key = ""
hotkey_p_key = ""
hotkey_c_key = ""
hotkey_n_p_key = ""
hotkey_p_p_key = ""
hotkey_home_key = ""
hotkey_reset_key = ""
-- script placeholders
script_sets = nil
script_props = nil
source_sets = nil
source_props = nil
hotkey_props = nil
--monitor variables
mon_song = ""
mon_lyric = ""
mon_verse = 0
mon_nextlyric = ""
mon_alt = ""
mon_nextalt = ""
mon_nextsong = ""
meta_tags = ""
source_meta_tags = ""
-- 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
text_status = TEXT_VISIBLE
text_opacity = 100
text_fade_speed = 1
text_fade_enabled = false
load_source = nil
expandcollapse = true
showhelp = false
transition_enabled = false -- transitions are a work in progress to support duplicate source mode (not very stable)
transition_completed = false
source_saved = false -- ick... A saved toggle to keep from repeating the save function for every song source. Works for now
editVisSet = false
-- simple debugging/print mechanism
DEBUG = false -- 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
--------
----------------
------------------------ CALLBACKS
----------------
--------
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 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 prev_prepared(pressed)
if not pressed then
return
end
if #prepared_songs == 0 then
return
end
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 next_prepared(pressed)
if not pressed then
return
end
if #prepared_songs == 0 then
return
end
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 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
function get_load_lyric_song()
local scene = obs.obs_frontend_get_current_scene()
local scene_items = obs.obs_scene_enum_items(scene) -- Get list of all items in this scene
local song = nil
if scene_items ~= nil then
for _, scene_item in ipairs(scene_items) do -- Loop through all scene source items
local source = obs.obs_sceneitem_get_source(scene_item) -- Get item source pointer
local source_id = obs.obs_source_get_unversioned_id(source) -- Get item source_id
if source_id == "Prepare_Lyrics" then -- Skip if not a Prepare_Lyric source item
local settings = obs.obs_source_get_settings(source) -- Get settings for this Prepare_Lyric source
song = obs.obs_data_get_string(settings, "song") -- Get index for this source (set earlier)
obs.obs_data_release(settings) -- release memory
end
end
end
obs.sceneitem_list_release(scene_items) -- Free scene list
return song
end
function home_prepared(pressed)
if not pressed then
return false
end
dbg_method("home_prepared")
using_source = false
page_index = 0
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 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 get_current_scene_name()
dbg_method("get_current_scene_name")
local scene = obs.obs_frontend_get_current_scene()
local current_scene = obs.obs_source_get_name(scene)
obs.obs_source_release(scene)
if current_scene ~= nil then
return current_scene
else
return "-"
end
end
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
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
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 prepared_songs[prepared_index] == name then
-- if this song is being displayed, then prepare it anew
prepare_song_by_name(name)
transition_lyric_text(false)
end
return true
end
-- callback for the delete song button
-- deletes the selected song and updates the UI
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 button clicked
function prepare_song_clicked(props, p)
dbg_method("prepare_song_clicked")
if #prepared_songs == 0 then
set_text_visibility(TEXT_HIDDEN)
end
prepared_songs[#prepared_songs + 1] = obs.obs_data_get_string(script_sets, "prop_directory_list")
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])
obs.obs_data_set_string(script_sets, "prop_prepared_list", prepared_songs[#prepared_songs])
obs.obs_properties_apply_settings(props, script_sets)
return true
end
function refresh_button_clicked(props, p)
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
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")
source_filter = false
load_source_song_directory(true)
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
-- Called with ANY change to the prepared song list
function prepare_selection_made(props, prop, settings)
obs.obs_property_set_description(
obs.obs_properties_get(props, "prep_grp"),
" Prepared Songs/Text (" .. #prepared_songs .. ")"
)
dbg_method("prepare_selection_made")
local name = obs.obs_data_get_string(settings, "prop_prepared_list")
using_source = false
prepare_selected(name)
return true
end
-- removes prepared songs
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
update_source_text() -- required for monitor page
-- clear the list
local prep_prop = obs.obs_properties_get(props, "prop_prepared_list")
obs.obs_property_list_clear(prep_prop)
obs.obs_data_set_string(script_sets, "prop_prepared_list", "")
obs.obs_properties_apply_settings(props, script_sets)
return true
end
-- prepares the song with the title {name}
function prepare_selected(name)
dbg_method("prepare_selected")
-- try to prepare song
if prepare_song_by_name(name) then
page_index = 1
if not using_source then
prepared_index = get_index_in_list(prepared_songs, name)
else
source_song_title = name
all_sources_fade = true
end
transition_lyric_text(using_source)
else
-- hide everything if unable to prepare song
-- TODO: clear lyrics entirely after text is hidden
set_text_visibility(TEXT_HIDDEN)
end
--update_source_text()
return true
end
-- called when selection is made from directory list
function preview_selection_made(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)
return true
end
-- callback for when open song in editor button is clicked
-- opens the song in the native 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
-- callback for when open songs folder button is clicked
-- opens the folder containing files of all the saved songs
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
-- applies current source opacity to the necessary sources
function apply_source_opacity()
-- dbg_method("apply_source_visiblity")
local settings = obs.obs_data_create()
obs.obs_data_set_int(settings, "opacity", text_opacity) -- Set new text opacity to zero
obs.obs_data_set_int(settings, "outline_opacity", text_opacity) -- Set new text outline opacity to zero
local source = obs.obs_get_source_by_name(source_name)
if source ~= nil then
obs.obs_source_update(source, settings)
end
obs.obs_source_release(source)
obs.obs_data_release(settings)
local settings = obs.obs_data_create()
obs.obs_data_set_int(settings, "opacity", text_opacity) -- Set new text opacity to zero
obs.obs_data_set_int(settings, "outline_opacity", text_opacity) -- Set new text outline opacity to zero
local alt_source = obs.obs_get_source_by_name(alternate_source_name)
if alt_source ~= nil then
obs.obs_source_update(alt_source, settings)
end
obs.obs_source_release(alt_source)
obs.obs_data_release(settings)
dbg_bool("All Sources Fade:", all_sources_fade)
dbg_bool("Link Text:", link_text)
if all_sources_fade then
local settings = obs.obs_data_create()
obs.obs_data_set_int(settings, "opacity", text_opacity) -- Set new text opacity to zero
obs.obs_data_set_int(settings, "outline_opacity", text_opacity) -- Set new text outline opacity to zero
local title_source = obs.obs_get_source_by_name(title_source_name)
if title_source ~= nil then
obs.obs_source_update(title_source, settings)
end
obs.obs_source_release(title_source)
obs.obs_data_release(settings)
local settings = obs.obs_data_create()
obs.obs_data_set_int(settings, "opacity", text_opacity) -- Set new text opacity to zero
obs.obs_data_set_int(settings, "outline_opacity", text_opacity) -- Set new text outline opacity to zero
local static_source = obs.obs_get_source_by_name(static_source_name)
if static_source ~= nil then
obs.obs_source_update(static_source, settings)
end
obs.obs_source_release(static_source)
obs.obs_data_release(settings)
end
if link_extras or all_sources_fade then
local extra_linked_list = obs.obs_properties_get(script_props, "extra_linked_list")
local count = obs.obs_property_list_item_count(extra_linked_list)
if count > 0 then
for i = 0, count - 1 do
local source_name = obs.obs_property_list_item_string(extra_linked_list, i) -- get extra source by name
dbg_inner(source_name)
local extra_source = obs.obs_get_source_by_name(source_name)
if extra_source ~= nil then
source_id = obs.obs_source_get_unversioned_id(extra_source)
if source_id == "text_gdiplus" or source_id == "text_ft2_source" then -- just another text object
local settings = obs.obs_data_create()
obs.obs_data_set_int(settings, "opacity", text_opacity) -- Set new text opacity to zero
obs.obs_data_set_int(settings, "outline_opacity", text_opacity) -- Set new text outline opacity to zero
obs.obs_source_update(extra_source, settings) -- merge new opacity values
obs.obs_data_release(settings)
else -- check for filter named "Color Correction"
local color_filter = obs.obs_source_get_filter_by_name(extra_source, "Color Correction")
if color_filter ~= nil then -- update filters opacity
local filter_settings = obs.obs_source_get_settings(color_filter)
obs.obs_data_set_double(filter_settings, "opacity", text_opacity / 100)
obs.obs_source_update(color_filter, filter_settings)
obs.obs_data_release(filter_settings)
obs.obs_source_release(color_filter)
else -- try to just change visibility in the scene
dbg_inner("No Filter")
local sceneSource = obs.obs_frontend_get_current_scene()
local sceneObj = obs.obs_scene_from_source(sceneSource)
local sceneItem = obs.obs_scene_find_source(sceneObj, source_name)
obs.obs_source_release(scene)
if text_opacity > 50 then
obs.obs_sceneitem_set_visible(sceneItem, true)
else
obs.obs_sceneitem_set_visible(sceneItem, false)
end
end
end
end
obs.obs_source_release(extra_source) -- release source ptr
end
end
end
end
-- changes the visibility of the text; called EVERY time text is to be
-- hidden or made visible; not called during transition
function set_text_visibility(end_status)
dbg_method("set_text_visibility")
-- if already at desired visibility, then exit
if text_status == end_status then
return
end
if end_status == TEXT_HIDE then
text_opacity = 0
text_status = end_status
apply_source_opacity()
return
elseif end_status == TEXT_SHOW then
text_opacity = 100
text_status = end_status
all_sources_fade = true -- prevent orphaned title/static if link is removed when hidden
apply_source_opacity()
return
end
if text_fade_enabled then
-- if fade enabled, begin fade in or out
if end_status == TEXT_HIDDEN then
text_status = TEXT_HIDING
elseif end_status == TEXT_VISIBLE then
text_status = TEXT_SHOWING
end
--all_sources_fade = true
start_fade_timer()
else -- change visibility immediately (fade or no fade)
if end_status == TEXT_HIDDEN then
text_opacity = 0
text_status = end_status
elseif end_status == TEXT_VISIBLE then
text_opacity = 100
text_status = end_status
all_sources_fade = true -- prevent orphaned title/static if link is removed when hidden
end
apply_source_opacity()
--update_source_text()
all_sources_fade = false
return
end
end
-- transition to the next lyrics, use fade if enabled
-- if lyrics are hidden, force_show set to trued will make them visible
function transition_lyric_text(force_show)
dbg_method("transition_lyric_text")
dbg_bool("force show", force_show)
-- update the lyrics display immediately on 2 conditions
-- a) the text is hidden or hiding, and we will not force it to show
-- b) text fade is not enabled
-- otherwise, start text transition out and update the lyrics once
-- fade out transition is complete
if (text_status == TEXT_HIDDEN or text_status == TEXT_HIDING) and not force_show then
update_source_text()
-- if text is done hiding, we can cancel the all_sources_fade
if text_status == TEXT_HIDDEN then
all_sources_fade = false
end
dbg_inner("hidden")
elseif not text_fade_enabled then
dbg_custom("Instant On")
-- if text fade is not enabled, then we can cancel the all_sources_fade
all_sources_fade = false
set_text_visibility(TEXT_VISIBLE) -- does update_source_text()
update_source_text()
dbg_inner("no text fade")
else -- initiate fade out/in
dbg_custom("Transition Timer")
text_status = TEXT_TRANSITION_OUT
start_fade_timer()
end
dbg_bool("using_source", using_source)
end
-- updates the selected lyrics
function update_source_text()
dbg_method("update_source_text")
dbg_custom("Page Index: " .. page_index)
local text = ""
local alttext = ""
local next_lyric = ""
local next_alternate = ""
local static = static_text
local mstatic = static -- save static for use with monitor
local title = ""
if alt_title ~= "" then
title = alt_title
else
if not using_source then
if prepared_index ~= nil and prepared_index ~= 0 then
dbg_custom("Update from prepared: " .. prepared_index)
title = prepared_songs[prepared_index]
end
else
dbg_custom("Updatefrom source: " .. source_song_title)
title = source_song_title
end
end
local source = obs.obs_get_source_by_name(source_name)
local alt_source = obs.obs_get_source_by_name(alternate_source_name)
local stat_source = obs.obs_get_source_by_name(static_source_name)
local title_source = obs.obs_get_source_by_name(title_source_name)
if using_source or (prepared_index ~= nil and prepared_index ~= 0) then
if #lyrics > 0 then
if lyrics[page_index] ~= nil then
text = lyrics[page_index]
end
end
if #alternate > 0 then
if alternate[page_index] ~= nil then
alttext = alternate[page_index]
end
end
if link_text then
if string.len(text) == 0 and string.len(alttext) == 0 then
--static = ""
--title = ""
end
end
end
-- update source texts
if source ~= nil then
dbg_inner("Title Load")
local settings = obs.obs_data_create()
obs.obs_data_set_string(settings, "text", text)
obs.obs_source_update(source, settings)
obs.obs_data_release(settings)
next_lyric = lyrics[page_index + 1]
if (next_lyric == nil) then
next_lyric = ""
end
end
if alt_source ~= nil then
local settings = obs.obs_data_create() -- setup TEXT settings with opacity values
obs.obs_data_set_string(settings, "text", alttext)
obs.obs_source_update(alt_source, settings)
obs.obs_data_release(settings)
next_alternate = alternate[page_index + 1]
if (next_alternate == nil) then
next_alternate = ""
end
end
if stat_source ~= nil then
local settings = obs.obs_data_create()
obs.obs_data_set_string(settings, "text", static)
obs.obs_source_update(stat_source, settings)
obs.obs_data_release(settings)
end
if title_source ~= nil then
local settings = obs.obs_data_create()
obs.obs_data_set_string(settings, "text", title)
obs.obs_source_update(title_source, settings)
obs.obs_data_release(settings)
end
-- release source references
obs.obs_source_release(source)
obs.obs_source_release(alt_source)
obs.obs_source_release(stat_source)
obs.obs_source_release(title_source)
local next_prepared = ""
if using_source then
next_prepared = prepared_songs[prepared_index] -- plan to go to current prepared song
elseif prepared_index ~= nil and prepared_index < #prepared_songs then
next_prepared = prepared_songs[prepared_index + 1] -- plan to go to next prepared song
else
if source_active then
next_prepared = source_song_title -- plan to go back to source loaded song
else
next_prepared = prepared_songs[1] -- plan to loop around to first prepared song
end
end
mon_verse = 0
if #verses ~= nil then --find valid page Index
for i = 1, #verses do
if page_index >= verses[i] + 1 then
mon_verse = i
end
end -- v = current verse number for this page
end
mon_song = title
mon_lyric = text:gsub("\n", "<br>• ")
mon_nextlyric = next_lyric:gsub("\n", "<br>• ")
mon_alt = alttext:gsub("\n", "<br>• ")
mon_nextalt = next_alternate:gsub("\n", "<br>• ")
mon_nextsong = next_prepared
update_monitor()
end
-- starts the fade timer
function start_fade_timer()
dbgsp("started fade timer")
obs.timer_add(fade_callback, 50)
end
-- function is called by the fade timer to increment/decrement opacity value manually
function fade_callback()
-- if not in a transitory state, exit callback
if text_status == TEXT_HIDDEN or text_status == TEXT_VISIBLE then
obs.remove_current_callback()
all_sources_fade = false
end
-- the amount we want to change opacity by
local opacity_delta = 1 + text_fade_speed
-- change opacity in the direction of transitory state
if text_status == TEXT_HIDING or text_status == TEXT_TRANSITION_OUT then
local new_opacity = text_opacity - opacity_delta
if new_opacity > 0 then
text_opacity = new_opacity
else
-- completed fade out, determine next move
text_opacity = 0
if text_status == TEXT_TRANSITION_OUT then
-- update to new lyric between fades
update_source_text()
-- begin transition back in
text_status = TEXT_TRANSITION_IN
else
text_status = TEXT_HIDDEN
end
end
elseif text_status == TEXT_SHOWING or text_status == TEXT_TRANSITION_IN then
local new_opacity = text_opacity + opacity_delta
if new_opacity < 100 then
text_opacity = new_opacity
else
-- completed fade in
text_opacity = 100
text_status = TEXT_VISIBLE
end
end
-- apply the new opacity
apply_source_opacity()
end
function prepare_song_by_index(index)
dbg_method("prepare_song_by_index")
if index <= #prepared_songs then
prepare_song_by_name(prepared_songs[index])
end
end
-- prepares lyrics of the song
function prepare_song_by_name(name)
dbg_method("prepare_song_by_name")
if name == nil then
return false
end
last_prepared_song = name
-- if using transition on lyric change, first transition
-- would be reset with new song prepared
transition_completed = false
-- load song lines
local song_lines = get_song_text(name)
if song_lines == nil then
return false
end
local cur_line = 1
local cur_aline = 1
local recordRefrain = false
local playRefrain = false
local use_alternate = false
local use_static = false
local showText = true
local commentBlock = false
local singleAlternate = false
local refrain = {}
local arefrain = {}
lyrics = {}
verses = {}
alternate = {}
static_text = ""
alt_title = ""
local adjusted_display_lines = display_lines
local refrain_display_lines = display_lines
local alternate_display_lines = display_lines
local displaySize = display_lines
for _, line in ipairs(song_lines) do
local new_lines = 1
local single_line = false
local comment_index = line:find("//%[") -- Look for comment block Set
if comment_index ~= nil then
commentBlock = true
line = line:sub(comment_index + 3)
end
comment_index = line:find("//]") -- Look for comment block Clear
if comment_index ~= nil then
commentBlock = false
line = line:sub(1, comment_index - 1)
new_lines = 0
end
if not commentBlock then
local comment_index = line:find("%s*//")
if comment_index ~= nil then
line = line:sub(1, comment_index - 1)
new_lines = 0
end
local alternate_index = line:find("#A%[")
if alternate_index ~= nil then
use_alternate = true
line = line:sub(1, alternate_index - 1)
new_lines = 0
end
alternate_index = line:find("#A]")
if alternate_index ~= nil then
use_alternate = false
line = line:sub(1, alternate_index - 1)
new_lines = 0
end
local static_index = line:find("#S%[")
if static_index ~= nil then
use_static = true
line = line:sub(1, static_index - 1)
new_lines = 0