diff --git a/conf/vods.json b/conf/vods.json new file mode 100755 index 0000000..a2375f5 --- /dev/null +++ b/conf/vods.json @@ -0,0 +1,698 @@ +{ + "alttp": [ + { + "id": "ot-seg-escape", + "category": "Optimal TTAS Segment", + "label": false, + "name": "Escape (OTTAS Seg)", + "chatName": "Escape (OTTAS Seg)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\01-escape.mp4", + "sceneItem": "4x3ph", + "length": 352, + "includeInShuffle": true + }, + { + "id": "ot-seg-eastern", + "category": "Optimal TTAS Segment", + "label": false, + "name": "Eastern (OTTAS Seg)", + "chatName": "Eastern (OTTAS Seg)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\02-eastern.mp4", + "sceneItem": "4x3ph", + "length": 277, + "includeInShuffle": true + }, + { + "id": "ot-seg-desert", + "category": "Optimal TTAS Segment", + "label": false, + "name": "Desert (OTTAS Seg)", + "chatName": "Desert (OTTAS Seg)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\03-desert.mp4", + "sceneItem": "4x3ph", + "length": 345, + "includeInShuffle": true + }, + { + "id": "ot-seg-hera", + "category": "Optimal TTAS Segment", + "label": false, + "name": "Hera (OTTAS Seg)", + "chatName": "Hera (OTTAS Seg)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\04-hera.mp4", + "sceneItem": "4x3ph", + "length": 299, + "includeInShuffle": true + }, + { + "id": "ot-seg-atower", + "category": "Optimal TTAS Segment", + "label": false, + "name": "ATower (OTTAS Seg)", + "chatName": "ATower (OTTAS Seg)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\05-atower.mp4", + "sceneItem": "4x3ph", + "length": 352, + "includeInShuffle": true + }, + { + "id": "ot-seg-pod", + "category": "Optimal TTAS Segment", + "label": false, + "name": "PoD (OTTAS Seg)", + "chatName": "PoD (OTTAS Seg)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\06-pod.mp4", + "sceneItem": "4x3ph", + "length": 309, + "includeInShuffle": true + }, + { + "id": "ot-seg-thieves", + "category": "Optimal TTAS Segment", + "label": false, + "name": "Thieves (OTTAS Seg)", + "chatName": "Thieves (OTTAS Seg)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\07-thieves.mp4", + "sceneItem": "4x3ph", + "length": 377, + "includeInShuffle": true + }, + { + "id": "ot-seg-skull", + "category": "Optimal TTAS Segment", + "label": false, + "name": "Skull (OTTAS Seg)", + "chatName": "Skull (OTTAS Seg)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\08-skull.mp4", + "sceneItem": "4x3ph", + "length": 267, + "includeInShuffle": true + }, + { + "id": "ot-seg-ice", + "category": "Optimal TTAS Segment", + "label": false, + "name": "Ice (OTTAS Seg)", + "chatName": "Ice (OTTAS Seg)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\09-ice.mp4", + "sceneItem": "4x3ph", + "length": 318, + "includeInShuffle": true + }, + { + "id": "ot-seg-swamp", + "category": "Optimal TTAS Segment", + "label": false, + "name": "Swamp (OTTAS Seg)", + "chatName": "Swamp (OTTAS Seg)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\10-swamp.mp4", + "sceneItem": "4x3ph", + "length": 345, + "includeInShuffle": true + }, + { + "id": "ot-seg-mire", + "category": "Optimal TTAS Segment", + "label": false, + "name": "Mire (OTTAS Seg)", + "chatName": "Mire (OTTAS Seg)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\11-mire.mp4", + "sceneItem": "4x3ph", + "length": 365, + "includeInShuffle": true + }, + { + "id": "ot-seg-trock", + "category": "Optimal TTAS Segment", + "label": false, + "name": "TRock (OTTAS Seg)", + "chatName": "TRock (OTTAS Seg)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\12-trock.mp4", + "sceneItem": "4x3ph", + "length": 361, + "includeInShuffle": true + }, + { + "id": "ot-seg-gtower", + "category": "Optimal TTAS Segment", + "label": false, + "name": "GTower (OTTAS Seg)", + "chatName": "GTower (OTTAS Seg)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\13-gtower.mp4", + "sceneItem": "4x3ph", + "length": 396, + "includeInShuffle": true + }, + { + "id": "ot-seg-ganon", + "category": "Optimal TTAS Segment", + "label": false, + "name": "Ganon (TTAS Seg)", + "chatName": "Ganon (TTAS Seg)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\14-ganon.mp4", + "sceneItem": "4x3ph", + "length": 108, + "includeInShuffle": true + }, + { + "id": "st-seg-escape", + "category": "Safe TTAS Segment", + "label": false, + "name": "Escape (STTAS Seg)", + "chatName": "Escape (STTAS Seg)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\01-escape.mp4", + "sceneItem": "4x3ph", + "length": 354, + "includeInShuffle": true + }, + { + "id": "st-seg-eastern", + "category": "Safe TTAS Segment", + "label": false, + "name": "Eastern (STTAS Seg)", + "chatName": "Eastern (STTAS Seg)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\02-eastern.mp4", + "sceneItem": "4x3ph", + "length": 281, + "includeInShuffle": true + }, + { + "id": "st-seg-desert", + "category": "Safe TTAS Segment", + "label": false, + "name": "Desert (STTAS Seg)", + "chatName": "Desert (STTAS Seg)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\03-desert.mp4", + "sceneItem": "4x3ph", + "length": 347, + "includeInShuffle": true + }, + { + "id": "st-seg-hera", + "category": "Safe TTAS Segment", + "label": false, + "name": "Hera (STTAS Seg)", + "chatName": "Hera (STTAS Seg)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\04-hera.mp4", + "sceneItem": "4x3ph", + "length": 303, + "includeInShuffle": true + }, + { + "id": "st-seg-atower", + "category": "Safe TTAS Segment", + "label": false, + "name": "Agah Tower (STTAS Seg)", + "chatName": "Agah Tower (STTAS Seg)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\05-atower.mp4", + "sceneItem": "4x3ph", + "length": 354, + "includeInShuffle": true + }, + { + "id": "st-seg-pod", + "category": "Safe TTAS Segment", + "label": false, + "name": "PoD (STTAS Seg)", + "chatName": "PoD (STTAS Seg)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\06-pod.mp4", + "sceneItem": "4x3ph", + "length": 310, + "includeInShuffle": true + }, + { + "id": "st-seg-thieves", + "category": "Safe TTAS Segment", + "label": false, + "name": "Thieves (STTAS Seg)", + "chatName": "Thieves (STTAS Seg)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\07-thieves.mp4", + "sceneItem": "4x3ph", + "length": 379, + "includeInShuffle": true + }, + { + "id": "st-seg-skull", + "category": "Safe TTAS Segment", + "label": false, + "name": "Skull (STTAS Seg)", + "chatName": "Skull (STTAS Seg)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\08-skull.mp4", + "sceneItem": "4x3ph", + "length": 270, + "includeInShuffle": true + }, + { + "id": "st-seg-ice", + "category": "Safe TTAS Segment", + "label": false, + "name": "Ice (STTAS Seg)", + "chatName": "Ice (STTAS Seg)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\09-ice.mp4", + "sceneItem": "4x3ph", + "length": 321, + "includeInShuffle": true + }, + { + "id": "st-seg-swamp", + "category": "Safe TTAS Segment", + "label": false, + "name": "Swamp (STTAS Seg)", + "chatName": "Swamp (STTAS Seg)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\10-swamp.mp4", + "sceneItem": "4x3ph", + "length": 351, + "includeInShuffle": true + }, + { + "id": "st-seg-mire", + "category": "Safe TTAS Segment", + "label": false, + "name": "Mire (STTAS Seg)", + "chatName": "Mire (STTAS Seg)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\11-mire.mp4", + "sceneItem": "4x3ph", + "length": 370, + "includeInShuffle": true + }, + { + "id": "st-seg-trock", + "category": "Safe TTAS Segment", + "label": false, + "name": "TRock (STTAS Seg)", + "chatName": "TRock (STTAS Seg)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\12-trock.mp4", + "sceneItem": "4x3ph", + "length": 364, + "includeInShuffle": true + }, + { + "id": "st-seg-gtower", + "category": "Safe TTAS Segment", + "label": false, + "name": "GTower (STTAS Seg)", + "chatName": "GTower (STTAS Seg)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\13-gtower.mp4", + "sceneItem": "4x3ph", + "length": 408, + "includeInShuffle": true + }, + { + "id": "nmg-gold-escape", + "category": "Any% NMG Gold Segment", + "label": "Any% NMG Gold Segment: Escape (5:52.43) [2018-09-19]", + "name": "Escape (NMG Gold)", + "chatName": "Escape (NMG Gold)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\01-[552.43]-2018-09-19-escape.mp4", + "sceneItem": "4x3ph", + "length": 360, + "includeInShuffle": true + }, + { + "id": "nmg-gold-eastern", + "category": "Any% NMG Gold Segment", + "label": "Any% NMG Gold Segment: Eastern (5:00.12) [2018-06-25]", + "name": "Eastern (NMG Gold)", + "chatName": "Eastern (NMG Gold)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\02-[500.12]-2018-06-25-eastern.mp4", + "sceneItem": "4x3ph", + "length": 302, + "includeInShuffle": true + }, + { + "id": "nmg-gold-desert", + "category": "Any% NMG Gold Segment", + "label": "Any% NMG Gold Segment: Desert (6:10.32) [2018-06-02]", + "name": "Desert (NMG Gold)", + "chatName": "Desert (NMG Gold)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\03-[610.32]-2018-06-02-desert.mp4", + "sceneItem": "4x3ph", + "length": 374, + "includeInShuffle": true + }, + { + "id": "nmg-gold-hera", + "category": "Any% NMG Gold Segment", + "label": "Any% NMG Gold Segment: Hera (5:28.83) [2018-06-01]", + "name": "Hera (NMG Gold)", + "chatName": "Hera (NMG Gold)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\04-[528.83]-2018-06-01-hera.mp4", + "sceneItem": "4x3ph", + "length": 332, + "includeInShuffle": true + }, + { + "id": "nmg-gold-atower", + "category": "Any% NMG Gold Segment", + "label": "Any% NMG Gold Segment: Agah Tower (5:13.45) [2018-09-07]", + "name": "Agah Tower (NMG Gold)", + "chatName": "Agah Tower (NMG Gold)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\05a-[513.45]-2018-09-07-atower.mp4", + "sceneItem": "4x3ph", + "length": 314, + "includeInShuffle": true + }, + { + "id": "nmg-gold-pod", + "category": "Any% NMG Gold Segment", + "label": "Any% NMG Gold Segment: Palace of Darkness (6:11.15) [2018-05-27]", + "name": "PoD (NMG Gold)", + "chatName": "PoD (NMG Gold)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\06-[611.15]-2018-05-27-pod.mp4", + "sceneItem": "4x3ph", + "length": 376, + "includeInShuffle": true + }, + { + "id": "nmg-gold-thieves", + "category": "Any% NMG Gold Segment", + "label": "Any% NMG Gold Segment: Thieves Town (7:08.37) [2018-07-01]", + "name": "Thieves (NMG Gold)", + "chatName": "Thieves (NMG Gold)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\07-[708.37]-2018-07-01-thieves.mp4", + "sceneItem": "4x3ph", + "length": 432, + "includeInShuffle": true + }, + { + "id": "nmg-gold-skull", + "category": "Any% NMG Gold Segment", + "label": "Any% NMG Gold Segment: Skull Woods (5:24.28) [2018-06-02]", + "name": "Skull (NMG Gold)", + "chatName": "Skull (NMG Gold)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\08-[524.28]-2018-06-02-skull.mp4", + "sceneItem": "4x3ph", + "length": 328, + "includeInShuffle": true + }, + { + "id": "nmg-gold-ice", + "category": "Any% NMG Gold Segment", + "label": "Any% NMG Gold Segment: Ice Palace (6:18.13) [2018-09-08]", + "name": "Ice (NMG Gold)", + "chatName": "Ice (NMG Gold)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\09-[618.13]-2018-09-08-ice.mp4", + "sceneItem": "4x3ph", + "length": 379, + "includeInShuffle": true + }, + { + "id": "nmg-gold-swamp", + "category": "Any% NMG Gold Segment", + "label": "Any% NMG Gold Segment: Swamp Palace (6:51.87) [2018-06-10]", + "name": "Swamp (NMG Gold)", + "chatName": "Swamp (NMG Gold)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\10-[651.87]-2018-06-10-swamp.mp4", + "sceneItem": "4x3ph", + "length": 418, + "includeInShuffle": true + }, + { + "id": "nmg-gold-mire", + "category": "Any% NMG Gold Segment", + "label": "Any% NMG Gold Segment: Misery Mire (7:07.17) [2018-06-10]", + "name": "Mire (NMG Gold)", + "chatName": "Mire (NMG Gold)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\11-[707.17]-2018-06-10-mire.mp4", + "sceneItem": "4x3ph", + "length": 432, + "includeInShuffle": true + }, + { + "id": "nmg-gold-trock", + "category": "Any% NMG Gold Segment", + "label": "Any% NMG Gold Segment: Turtle Rock (7:07.34) [2018-06-25]", + "name": "TRock (NMG Gold)", + "chatName": "TRock (NMG Gold)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\12-[707.34]-2018-06-25-trock.mp4", + "sceneItem": "4x3ph", + "length": 434, + "includeInShuffle": true + }, + { + "id": "nmg-gold-gtower", + "category": "Any% NMG Gold Segment", + "label": "Any% NMG Gold Segment: Ganon's Tower (7:09.85) [2018-08-19]", + "name": "GTower (NMG Gold)", + "chatName": "GTower (NMG Gold)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\13-[709.85]-2018-08-19-gtower.mp4", + "sceneItem": "4x3ph", + "length": 433, + "includeInShuffle": true + }, + { + "id": "nmg-gold-ganon", + "category": "Any% NMG Gold Segment", + "label": "Any% NMG Gold Segment: Ganon (1:43.06) [2018-05-30]", + "name": "Ganon (NMG Gold)", + "chatName": "Ganon (NMG Gold)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\14-[143.06]-2018-05-30-ganon.mp4", + "sceneItem": "4x3ph", + "length": 117, + "includeInShuffle": true + }, + { + "id": "pb-100-ahp", + "category": "Personal Best", + "label": "Personal Best: 100% All Heart Pieces (1:19:12) [2017-12-22]", + "name": "100% MG AHP (PB)", + "chatName": "100% MG AHP (PB)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\100%-mg-ahp\\2017-12-22-100mg-11912.mp4", + "sceneItem": "16x9ph", + "length": 4786, + "includeInShuffle": true + }, + { + "id": "pb-ab", + "category": "Personal Best", + "label": "Personal Best: All Bosses No EG (1:09:23) [2017-11-20]", + "name": "All Bosses (PB)", + "chatName": "All Bosses (PB)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\all-bosses\\2017-11-20-ab-10923.mp4", + "sceneItem": "16x9ph", + "length": 4200, + "includeInShuffle": false + }, + { + "id": "pb-ad", + "category": "Personal Best", + "label": "Personal Best: All Dungeons No EG/DW/WW (1:14:59) [2017-11-19]", + "name": "All Dungeons (PB)", + "chatName": "All Dungeons (PB)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\all-dungeons\\2017-11-19-ad-11459.mp4", + "sceneItem": "16x9ph", + "length": 4555, + "includeInShuffle": true + }, + { + "id": "pb-any-nmg", + "category": "Personal Best", + "label": "Personal Best: Any% NMG No S+Q (1:26:24) [2018-05-27]", + "name": "Any% NMG (PB)", + "chatName": "Any% NMG (PB)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\any%-nmg-nsq\\2018-05-27-nmg-12624.mp4", + "sceneItem": "16x9ph", + "length": 5190, + "includeInShuffle": true + }, + { + "id": "pb-any-no-eg", + "category": "Personal Best", + "label": "Personal Best: Any% No EG (30:28) [2018-09-09]", + "name": "Any% No EG (PB)", + "chatName": "Any% No EG (PB)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\any%-no-eg\\2018-09-09-no-eg-3028.mp4", + "sceneItem": "16x9ph", + "length": 1833, + "includeInShuffle": true + }, + { + "id": "pb-master-sword", + "category": "Personal Best", + "label": "Personal Best: Master Sword NMG (22:23) [2018-08-23]", + "name": "Master Sword (PB)", + "chatName": "Master Sword (PB)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\master-sword\\2018-08-23-master-sword-2223.mp4", + "sceneItem": "16x9ph", + "length": 1409, + "includeInShuffle": true + }, + { + "id": "pb-ms", + "category": "Personal Best", + "label": "Personal Best: Mirror Shield NMG (50:32) [2017-06-20]", + "name": "Mirror Shield (PB)", + "chatName": "Mirror Shield (PB)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\mirror-shield\\2017-06-20-mirror-shield-5032.mp4", + "sceneItem": "16x9ph", + "length": 3068, + "includeInShuffle": true + }, + { + "id": "pb-ms-no-eg", + "category": "Personal Best", + "label": "Personal Best: Mirror Shield No EG (11:38) [2017-07-02]", + "name": "Mirror Shield No EG (PB)", + "chatName": "Mirror Shield No EG (PB)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\mirror-shield-no-eg\\2017-07-02-mirror-shield-no-eg-1138.mp4", + "sceneItem": "16x9ph", + "length": 738, + "includeInShuffle": true + }, + { + "id": "pb-rbo", + "category": "Personal Best", + "label": "Personal Best: Reverse Boss Order (1:18:13) [2017-12-01]", + "name": "Reverse Boss Order (PB)", + "chatName": "Reverse Boss Order (PB)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\rbo\\2017-12-01-rbo-11813.mp4", + "sceneItem": "16x9ph", + "length": 4725, + "includeInShuffle": true + }, + { + "id": "pb-100-nmg", + "category": "Personal Best", + "label": "Personal Best: 100% NMG (1:49:30) [2017-01-31]", + "name": "100% NMG (PB)", + "chatName": "100% NMG (PB)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\100%\\2017-01-31-hundo-14930.mp4", + "sceneItem": "legacyph", + "length": 6598, + "includeInShuffle": false + }, + { + "id": "pb-dg", + "category": "Personal Best", + "label": "Personal Best: Defeat Ganon (13:41) [2017-02-20]", + "name": "Defeat Ganon (PB)", + "chatName": "Defeat Ganon (PB)", + "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\defeat-ganon\\2017-02-20_12-55-40-defeat-ganon-1341.mp4", + "sceneItem": "legacyph", + "length": 868, + "includeInShuffle": true + } + ], + "memes": [ + { + "id": "auw", + "name": "auw", + "filePath": "Y:\\media\\videos\\black-box-everybody-wow.mp4", + "sceneItem": "everybody-wow", + "length": 247, + "includeInShuffle": true + }, + { + "id": "archery", + "name": "archery", + "filePath": "Y:\\media\\videos\\ALttP\\memes\\archery-contest.mp4", + "sceneItem": "meme1", + "length": 27, + "includeInShuffle": true + }, + { + "id": "69blazeit", + "name": "69blazeit", + "filePath": "Y:\\media\\videos\\ALttP\\memes\\69BlazeIt.mp4", + "sceneItem": "meme1", + "length": 92, + "includeInShuffle": true + }, + { + "id": "rpgfarm", + "name": "rpgfarm", + "filePath": "Y:\\media\\videos\\ALttP\\memes\\2016-07-24-1424-06-rpg-race-sniped.mp4", + "sceneItem": "meme1", + "length": 144, + "includeInShuffle": true + }, + { + "id": "emmapeg", + "name": "emmapeg", + "filePath": "Y:\\media\\videos\\ALttP\\memes\\emma-pegging.mp4", + "sceneItem": "meme1", + "length": 8, + "includeInShuffle": true + }, + { + "id": "handy", + "name": "handy", + "filePath": "Y:\\media\\videos\\ALttP\\memes\\handy-in-the-mothhole.mp4", + "sceneItem": "meme1", + "length": 39, + "includeInShuffle": true + }, + { + "id": "bodyguard", + "name": "bodyguard", + "filePath": "Y:\\media\\videos\\ALttP\\memes\\heroic-popo.mp4", + "sceneItem": "meme1", + "length": 14, + "includeInShuffle": true + }, + { + "id": "whowillitbe", + "name": "whowillitbe", + "filePath": "Y:\\media\\videos\\ALttP\\memes\\its-gonna-be-may.mp4", + "sceneItem": "meme1", + "length": 30, + "includeInShuffle": true + }, + { + "id": "mindblown", + "name": "mindblown", + "filePath": "Y:\\media\\videos\\ALttP\\memes\\mindblowing-and-lifechanging.mp4", + "sceneItem": "meme1", + "length": 55, + "includeInShuffle": true + }, + { + "id": "nerd-nookie", + "name": "nerd-nookie", + "filePath": "Y:\\media\\videos\\ALttP\\memes\\nerd-bizkit.mp4", + "sceneItem": "meme1", + "length": 27, + "includeInShuffle": true + }, + { + "id": "curling", + "name": "curling", + "filePath": "Y:\\media\\videos\\ALttP\\curling-bored-janitors.mp4", + "sceneItem": "meme1", + "length": 16, + "includeInShuffle": true + }, + { + "id": "airplane", + "name": "airplane", + "filePath": "Y:\\media\\videos\\ALttP\\emetaPlane.mp4", + "sceneItem": "meme1", + "length": 5, + "includeInShuffle": true + }, + { + "id": "hard-things", + "name": "hard-things", + "filePath": "Y:\\media\\videos\\ALttP\\questions-about-hard-things.mp4", + "sceneItem": "meme1", + "length": 39, + "includeInShuffle": true + }, + { + "id": "18arrows", + "name": "18arrows", + "filePath": "Y:\\media\\videos\\ALttP\\screevo-18-arrows-fine.mp4", + "sceneItem": "meme1", + "length": 41, + "includeInShuffle": true + }, + { + "id": "quake", + "name": "quake", + "filePath": "Y:\\media\\videos\\ALttP\\trock-indoor-quake.mp4", + "sceneItem": "meme1", + "length": 17, + "includeInShuffle": true + } + ] +} \ No newline at end of file diff --git a/config.json b/config.json index 33ba804..ba8b1bd 100755 --- a/config.json +++ b/config.json @@ -9,50 +9,26 @@ "botChannel": "bot", "textCmdCooldown": 5, "twitch": { - "ircServer" : "irc.chat.twitch.tv", - "username": "greenhambot", - "oauth": "oauth:90aunml4vwyt7zgulm7s5m7xt4hnpc", - "channels": ["#greenham"], - "cmdPrefix": "$", - "blacklistedUsers": [], - "admins": ["greenham","greenhambot"], + "channel": "#greenham", + "botLogin": { + "username": "greenhambot", + "oauth": "oauth:90aunml4vwyt7zgulm7s5m7xt4hnpc" + }, "editorLogin": { "username": "greenham", "oauth": "oauth:gmi4sjl3k0we0d4gsppkrcqcjpdma6" - } + }, + "cmdPrefix": "$", + "admins": ["greenham","greenhambot"], + "blacklistedUsers": [], + "ircServer" : "irc.chat.twitch.tv", + "debug": false }, "obs": { "websocket": { "address": "192.168.0.111:4444", "password": "goodnewseveryone" - }, - "availablePlaylists": [ - { - "sceneItem": "ttas-segments", - "activity": "TTAS Segments !ttas", - "name": "TTAS Segments", - "chatName": "TTAS Segments" - }, - { - "sceneItem": "room-grind", - "activity": "TTAS Room Grind !ttas", - "name": "TTAS Room Grind", - "chatName": "TTAS Room Grind" - }, - { - "sceneItem": "gold-segments", - "activity": "Any% NMG Gold Segments", - "name": "NMG Golds", - "chatName": "NMG Golds" - }, - { - "sceneItem": "personal-bests", - "activity": "Past Personal Bests", - "name": "Personal Bests", - "chatName": "Personal Bests" - } - ], - "defaultPlaylist": "room-grind" + } }, "defaultSceneName": "fgfm", "commercialSceneName": "commercial", @@ -67,943 +43,6 @@ "commercialInterval": 3600, "auwChance": 25, "defaultSRVolume": 75, - "vods": [ - { - "id": "ot-seg-escape", - "category": "Optimal TTAS Segment", - "label": false, - "name": "Escape (OTTAS Seg)", - "chatName": "Escape (OTTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\01-escape.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 352, - "includeInShuffle": true - }, - { - "id": "ot-seg-eastern", - "category": "Optimal TTAS Segment", - "label": false, - "name": "Eastern (OTTAS Seg)", - "chatName": "Eastern (OTTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\02-eastern.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 277, - "includeInShuffle": true - }, - { - "id": "ot-seg-desert", - "category": "Optimal TTAS Segment", - "label": false, - "name": "Desert (OTTAS Seg)", - "chatName": "Desert (OTTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\03-desert.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 345, - "includeInShuffle": true - }, - { - "id": "ot-seg-hera", - "category": "Optimal TTAS Segment", - "label": false, - "name": "Hera (OTTAS Seg)", - "chatName": "Hera (OTTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\04-hera.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 299, - "includeInShuffle": true - }, - { - "id": "ot-seg-atower", - "category": "Optimal TTAS Segment", - "label": false, - "name": "ATower (OTTAS Seg)", - "chatName": "ATower (OTTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\05-atower.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 352, - "includeInShuffle": true - }, - { - "id": "ot-seg-pod", - "category": "Optimal TTAS Segment", - "label": false, - "name": "PoD (OTTAS Seg)", - "chatName": "PoD (OTTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\06-pod.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 309, - "includeInShuffle": true - }, - { - "id": "ot-seg-thieves", - "category": "Optimal TTAS Segment", - "label": false, - "name": "Thieves (OTTAS Seg)", - "chatName": "Thieves (OTTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\07-thieves.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 377, - "includeInShuffle": true - }, - { - "id": "ot-seg-skull", - "category": "Optimal TTAS Segment", - "label": false, - "name": "Skull (OTTAS Seg)", - "chatName": "Skull (OTTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\08-skull.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 267, - "includeInShuffle": true - }, - { - "id": "ot-seg-ice", - "category": "Optimal TTAS Segment", - "label": false, - "name": "Ice (OTTAS Seg)", - "chatName": "Ice (OTTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\09-ice.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 318, - "includeInShuffle": true - }, - { - "id": "ot-seg-swamp", - "category": "Optimal TTAS Segment", - "label": false, - "name": "Swamp (OTTAS Seg)", - "chatName": "Swamp (OTTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\10-swamp.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 345, - "includeInShuffle": true - }, - { - "id": "ot-seg-mire", - "category": "Optimal TTAS Segment", - "label": false, - "name": "Mire (OTTAS Seg)", - "chatName": "Mire (OTTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\11-mire.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 365, - "includeInShuffle": true - }, - { - "id": "ot-seg-trock", - "category": "Optimal TTAS Segment", - "label": false, - "name": "TRock (OTTAS Seg)", - "chatName": "TRock (OTTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\12-trock.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 361, - "includeInShuffle": true - }, - { - "id": "ot-seg-gtower", - "category": "Optimal TTAS Segment", - "label": false, - "name": "GTower (OTTAS Seg)", - "chatName": "GTower (OTTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\13-gtower.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 396, - "includeInShuffle": true - }, - { - "id": "ot-seg-ganon", - "category": "Optimal TTAS Segment", - "label": false, - "name": "Ganon (TTAS Seg)", - "chatName": "Ganon (TTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\14-ganon.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 108, - "includeInShuffle": true - }, - { - "id": "st-seg-escape", - "category": "Safe TTAS Segment", - "label": false, - "name": "Escape (STTAS Seg)", - "chatName": "Escape (STTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\01-escape.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 354, - "includeInShuffle": true - }, - { - "id": "st-seg-eastern", - "category": "Safe TTAS Segment", - "label": false, - "name": "Eastern (STTAS Seg)", - "chatName": "Eastern (STTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\02-eastern.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 281, - "includeInShuffle": true - }, - { - "id": "st-seg-desert", - "category": "Safe TTAS Segment", - "label": false, - "name": "Desert (STTAS Seg)", - "chatName": "Desert (STTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\03-desert.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 347, - "includeInShuffle": true - }, - { - "id": "st-seg-hera", - "category": "Safe TTAS Segment", - "label": false, - "name": "Hera (STTAS Seg)", - "chatName": "Hera (STTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\04-hera.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 303, - "includeInShuffle": true - }, - { - "id": "st-seg-atower", - "category": "Safe TTAS Segment", - "label": false, - "name": "Agah Tower (STTAS Seg)", - "chatName": "Agah Tower (STTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\05-atower.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 354, - "includeInShuffle": true - }, - { - "id": "st-seg-pod", - "category": "Safe TTAS Segment", - "label": false, - "name": "PoD (STTAS Seg)", - "chatName": "PoD (STTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\06-pod.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 310, - "includeInShuffle": true - }, - { - "id": "st-seg-thieves", - "category": "Safe TTAS Segment", - "label": false, - "name": "Thieves (STTAS Seg)", - "chatName": "Thieves (STTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\07-thieves.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 379, - "includeInShuffle": true - }, - { - "id": "st-seg-skull", - "category": "Safe TTAS Segment", - "label": false, - "name": "Skull (STTAS Seg)", - "chatName": "Skull (STTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\08-skull.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 270, - "includeInShuffle": true - }, - { - "id": "st-seg-ice", - "category": "Safe TTAS Segment", - "label": false, - "name": "Ice (STTAS Seg)", - "chatName": "Ice (STTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\09-ice.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 321, - "includeInShuffle": true - }, - { - "id": "st-seg-swamp", - "category": "Safe TTAS Segment", - "label": false, - "name": "Swamp (STTAS Seg)", - "chatName": "Swamp (STTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\10-swamp.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 351, - "includeInShuffle": true - }, - { - "id": "st-seg-mire", - "category": "Safe TTAS Segment", - "label": false, - "name": "Mire (STTAS Seg)", - "chatName": "Mire (STTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\11-mire.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 370, - "includeInShuffle": true - }, - { - "id": "st-seg-trock", - "category": "Safe TTAS Segment", - "label": false, - "name": "TRock (STTAS Seg)", - "chatName": "TRock (STTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\12-trock.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 364, - "includeInShuffle": true - }, - { - "id": "st-seg-gtower", - "category": "Safe TTAS Segment", - "label": false, - "name": "GTower (STTAS Seg)", - "chatName": "GTower (STTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\13-gtower.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 408, - "includeInShuffle": true - }, - { - "id": "nmg-gold-escape", - "category": "Any% NMG Gold Segment", - "label": "Any% NMG Gold Segment: Escape (5:52.43) [2018-09-19]", - "name": "Escape (NMG Gold)", - "chatName": "Escape (NMG Gold)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\01-[552.43]-2018-09-19-escape.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 360, - "includeInShuffle": true - }, - { - "id": "nmg-gold-eastern", - "category": "Any% NMG Gold Segment", - "label": "Any% NMG Gold Segment: Eastern (5:00.12) [2018-06-25]", - "name": "Eastern (NMG Gold)", - "chatName": "Eastern (NMG Gold)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\02-[500.12]-2018-06-25-eastern.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 302, - "includeInShuffle": true - }, - { - "id": "nmg-gold-desert", - "category": "Any% NMG Gold Segment", - "label": "Any% NMG Gold Segment: Desert (6:10.32) [2018-06-02]", - "name": "Desert (NMG Gold)", - "chatName": "Desert (NMG Gold)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\03-[610.32]-2018-06-02-desert.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 374, - "includeInShuffle": true - }, - { - "id": "nmg-gold-hera", - "category": "Any% NMG Gold Segment", - "label": "Any% NMG Gold Segment: Hera (5:28.83) [2018-06-01]", - "name": "Hera (NMG Gold)", - "chatName": "Hera (NMG Gold)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\04-[528.83]-2018-06-01-hera.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 332, - "includeInShuffle": true - }, - { - "id": "nmg-gold-atower", - "category": "Any% NMG Gold Segment", - "label": "Any% NMG Gold Segment: Agah Tower (5:13.45) [2018-09-07]", - "name": "Agah Tower (NMG Gold)", - "chatName": "Agah Tower (NMG Gold)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\05a-[513.45]-2018-09-07-atower.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 314, - "includeInShuffle": true - }, - { - "id": "nmg-gold-pod", - "category": "Any% NMG Gold Segment", - "label": "Any% NMG Gold Segment: Palace of Darkness (6:11.15) [2018-05-27]", - "name": "PoD (NMG Gold)", - "chatName": "PoD (NMG Gold)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\06-[611.15]-2018-05-27-pod.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 376, - "includeInShuffle": true - }, - { - "id": "nmg-gold-thieves", - "category": "Any% NMG Gold Segment", - "label": "Any% NMG Gold Segment: Thieves Town (7:08.37) [2018-07-01]", - "name": "Thieves (NMG Gold)", - "chatName": "Thieves (NMG Gold)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\07-[708.37]-2018-07-01-thieves.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 432, - "includeInShuffle": true - }, - { - "id": "nmg-gold-skull", - "category": "Any% NMG Gold Segment", - "label": "Any% NMG Gold Segment: Skull Woods (5:24.28) [2018-06-02]", - "name": "Skull (NMG Gold)", - "chatName": "Skull (NMG Gold)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\08-[524.28]-2018-06-02-skull.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 328, - "includeInShuffle": true - }, - { - "id": "nmg-gold-ice", - "category": "Any% NMG Gold Segment", - "label": "Any% NMG Gold Segment: Ice Palace (6:18.13) [2018-09-08]", - "name": "Ice (NMG Gold)", - "chatName": "Ice (NMG Gold)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\09-[618.13]-2018-09-08-ice.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 379, - "includeInShuffle": true - }, - { - "id": "nmg-gold-swamp", - "category": "Any% NMG Gold Segment", - "label": "Any% NMG Gold Segment: Swamp Palace (6:51.87) [2018-06-10]", - "name": "Swamp (NMG Gold)", - "chatName": "Swamp (NMG Gold)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\10-[651.87]-2018-06-10-swamp.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 418, - "includeInShuffle": true - }, - { - "id": "nmg-gold-mire", - "category": "Any% NMG Gold Segment", - "label": "Any% NMG Gold Segment: Misery Mire (7:07.17) [2018-06-10]", - "name": "Mire (NMG Gold)", - "chatName": "Mire (NMG Gold)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\11-[707.17]-2018-06-10-mire.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 432, - "includeInShuffle": true - }, - { - "id": "nmg-gold-trock", - "category": "Any% NMG Gold Segment", - "label": "Any% NMG Gold Segment: Turtle Rock (7:07.34) [2018-06-25]", - "name": "TRock (NMG Gold)", - "chatName": "TRock (NMG Gold)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\12-[707.34]-2018-06-25-trock.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 434, - "includeInShuffle": true - }, - { - "id": "nmg-gold-gtower", - "category": "Any% NMG Gold Segment", - "label": "Any% NMG Gold Segment: Ganon's Tower (7:09.85) [2018-08-19]", - "name": "GTower (NMG Gold)", - "chatName": "GTower (NMG Gold)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\13-[709.85]-2018-08-19-gtower.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 433, - "includeInShuffle": true - }, - { - "id": "nmg-gold-ganon", - "category": "Any% NMG Gold Segment", - "label": "Any% NMG Gold Segment: Ganon (1:43.06) [2018-05-30]", - "name": "Ganon (NMG Gold)", - "chatName": "Ganon (NMG Gold)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\14-[143.06]-2018-05-30-ganon.mp4", - "sceneItemProperties": { - "crop.left": 0, - "position.x": 320, - "scale.x": 960 - }, - "sceneItem": "4x3ph", - "length": 117, - "includeInShuffle": true - }, - { - "id": "pb-100-ahp", - "category": "Personal Best", - "label": "Personal Best: 100% All Heart Pieces (1:19:12) [2017-12-22]", - "name": "100% MG AHP (PB)", - "chatName": "100% MG AHP (PB)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\100%-mg-ahp\\2017-12-22-100mg-11912.mp4", - "sceneItemProperties": { - "crop.left": 320, - "position.x": 320, - "scale.x": 1280 - }, - "sceneItem": "16x9ph", - "length": 4786, - "includeInShuffle": true - }, - { - "id": "pb-ab", - "category": "Personal Best", - "label": "Personal Best: All Bosses No EG (1:09:23) [2017-11-20]", - "name": "All Bosses (PB)", - "chatName": "All Bosses (PB)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\all-bosses\\2017-11-20-ab-10923.mp4", - "sceneItemProperties": { - "crop.left": 320, - "position.x": 320, - "scale.x": 1280 - }, - "sceneItem": "16x9ph", - "length": 4200, - "includeInShuffle": false - }, - { - "id": "pb-ad", - "category": "Personal Best", - "label": "Personal Best: All Dungeons No EG/DW/WW (1:14:59) [2017-11-19]", - "name": "All Dungeons (PB)", - "chatName": "All Dungeons (PB)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\all-dungeons\\2017-11-19-ad-11459.mp4", - "sceneItemProperties": { - "crop.left": 320, - "position.x": 320, - "scale.x": 1280 - }, - "sceneItem": "16x9ph", - "length": 4555, - "includeInShuffle": true - }, - { - "id": "pb-any-nmg", - "category": "Personal Best", - "label": "Personal Best: Any% NMG No S+Q (1:26:24) [2018-05-27]", - "name": "Any% NMG (PB)", - "chatName": "Any% NMG (PB)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\any%-nmg-nsq\\2018-05-27-nmg-12624.mp4", - "sceneItemProperties": { - "crop.left": 320, - "position.x": 320, - "scale.x": 1280 - }, - "sceneItem": "16x9ph", - "length": 5190, - "includeInShuffle": true - }, - { - "id": "pb-any-no-eg", - "category": "Personal Best", - "label": "Personal Best: Any% No EG (30:28) [2018-09-09]", - "name": "Any% No EG (PB)", - "chatName": "Any% No EG (PB)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\any%-no-eg\\2018-09-09-no-eg-3028.mp4", - "sceneItemProperties": { - "crop.left": 320, - "position.x": 320, - "scale.x": 1280 - }, - "sceneItem": "16x9ph", - "length": 1833, - "includeInShuffle": true - }, - { - "id": "pb-master-sword", - "category": "Personal Best", - "label": "Personal Best: Master Sword NMG (22:23) [2018-08-23]", - "name": "Master Sword (PB)", - "chatName": "Master Sword (PB)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\master-sword\\2018-08-23-master-sword-2223.mp4", - "sceneItemProperties": { - "crop.left": 320, - "position.x": 320, - "scale.x": 1280 - }, - "sceneItem": "16x9ph", - "length": 1409, - "includeInShuffle": true - }, - { - "id": "pb-ms", - "category": "Personal Best", - "label": "Personal Best: Mirror Shield NMG (50:32) [2017-06-20]", - "name": "Mirror Shield (PB)", - "chatName": "Mirror Shield (PB)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\mirror-shield\\2017-06-20-mirror-shield-5032.mp4", - "sceneItemProperties": { - "crop.left": 320, - "position.x": 320, - "scale.x": 1280 - }, - "sceneItem": "16x9ph", - "length": 3068, - "includeInShuffle": true - }, - { - "id": "pb-ms-no-eg", - "category": "Personal Best", - "label": "Personal Best: Mirror Shield No EG (11:38) [2017-07-02]", - "name": "Mirror Shield No EG (PB)", - "chatName": "Mirror Shield No EG (PB)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\mirror-shield-no-eg\\2017-07-02-mirror-shield-no-eg-1138.mp4", - "sceneItemProperties": { - "crop.left": 320, - "position.x": 320, - "scale.x": 1280 - }, - "sceneItem": "16x9ph", - "length": 738, - "includeInShuffle": true - }, - { - "id": "pb-rbo", - "category": "Personal Best", - "label": "Personal Best: Reverse Boss Order (1:18:13) [2017-12-01]", - "name": "Reverse Boss Order (PB)", - "chatName": "Reverse Boss Order (PB)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\rbo\\2017-12-01-rbo-11813.mp4", - "sceneItemProperties": { - "crop.left": 320, - "position.x": 320, - "scale.x": 1280 - }, - "sceneItem": "16x9ph", - "length": 4725, - "includeInShuffle": true - }, - { - "id": "pb-100-nmg", - "category": "Personal Best", - "label": "Personal Best: 100% NMG (1:49:30) [2017-01-31]", - "name": "100% NMG (PB)", - "chatName": "100% NMG (PB)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\100%\\2017-01-31-hundo-14930.mp4", - "sceneItem": "legacyph", - "length": 6598, - "includeInShuffle": false - }, - { - "id": "pb-dg", - "category": "Personal Best", - "label": "Personal Best: Defeat Ganon (13:41) [2017-02-20]", - "name": "Defeat Ganon (PB)", - "chatName": "Defeat Ganon (PB)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\defeat-ganon\\2017-02-20_12-55-40-defeat-ganon-1341.mp4", - "sceneItem": "legacyph", - "length": 868, - "includeInShuffle": true - } - ], - "memes": [ - { - "id": "archery", - "name": "archery", - "filePath": "Y:\\media\\videos\\ALttP\\memes\\archery-contest.mp4", - "sceneItem": "meme1", - "length": 27, - "includeInShuffle": true - }, - { - "id": "69blazeit", - "name": "69blazeit", - "filePath": "Y:\\media\\videos\\ALttP\\memes\\69BlazeIt.mp4", - "sceneItem": "meme1", - "length": 92, - "includeInShuffle": true - }, - { - "id": "rpgfarm", - "name": "rpgfarm", - "filePath": "Y:\\media\\videos\\ALttP\\memes\\2016-07-24-1424-06-rpg-race-sniped.mp4", - "sceneItem": "meme1", - "length": 144, - "includeInShuffle": true - }, - { - "id": "emmapeg", - "name": "emmapeg", - "filePath": "Y:\\media\\videos\\ALttP\\memes\\emma-pegging.mp4", - "sceneItem": "meme1", - "length": 8, - "includeInShuffle": true - }, - { - "id": "handy", - "name": "handy", - "filePath": "Y:\\media\\videos\\ALttP\\memes\\handy-in-the-mothhole.mp4", - "sceneItem": "meme1", - "length": 39, - "includeInShuffle": true - }, - { - "id": "bodyguard", - "name": "bodyguard", - "filePath": "Y:\\media\\videos\\ALttP\\memes\\heroic-popo.mp4", - "sceneItem": "meme1", - "length": 14, - "includeInShuffle": true - }, - { - "id": "whowillitbe", - "name": "whowillitbe", - "filePath": "Y:\\media\\videos\\ALttP\\memes\\its-gonna-be-may.mp4", - "sceneItem": "meme1", - "length": 30, - "includeInShuffle": true - }, - { - "id": "mindblown", - "name": "mindblown", - "filePath": "Y:\\media\\videos\\ALttP\\memes\\mindblowing-and-lifechanging.mp4", - "sceneItem": "meme1", - "length": 55, - "includeInShuffle": true - }, - { - "id": "nerd-nookie", - "name": "nerd-nookie", - "filePath": "Y:\\media\\videos\\ALttP\\memes\\nerd-bizkit.mp4", - "sceneItem": "meme1", - "length": 27, - "includeInShuffle": true - }, - { - "id": "curling", - "name": "curling", - "filePath": "Y:\\media\\videos\\ALttP\\curling-bored-janitors.mp4", - "sceneItem": "meme1", - "length": 16, - "includeInShuffle": true - }, - { - "id": "airplane", - "name": "airplane", - "filePath": "Y:\\media\\videos\\ALttP\\emetaPlane.mp4", - "sceneItem": "meme1", - "length": 5, - "includeInShuffle": true - }, - { - "id": "hard-things", - "name": "hard-things", - "filePath": "Y:\\media\\videos\\ALttP\\questions-about-hard-things.mp4", - "sceneItem": "meme1", - "length": 39, - "includeInShuffle": true - }, - { - "id": "18arrows", - "name": "18arrows", - "filePath": "Y:\\media\\videos\\ALttP\\screevo-18-arrows-fine.mp4", - "sceneItem": "meme1", - "length": 41, - "includeInShuffle": true - }, - { - "id": "quake", - "name": "quake", - "filePath": "Y:\\media\\videos\\ALttP\\trock-indoor-quake.mp4", - "sceneItem": "meme1", - "length": 17, - "includeInShuffle": true - } - ], + "vodConfigFile": "./conf/vods.json", "debug": false } \ No newline at end of file diff --git a/fgfm.TODO b/fgfm.TODO index f124925..eccb6d1 100755 --- a/fgfm.TODO +++ b/fgfm.TODO @@ -1,7 +1,6 @@ TODO: + ☐ Modularize OBS and Twitch code ☐ Room vid requests / import - ✔ Add random chance for room grind playlist to show for certain amount of time @done (18-09-21 12:28) - ☐ modularize OBS and Twitch code ☐ Rotating background images (leftside) ☐ Stream alerts for chat ☐ support for $pause @@ -17,11 +16,12 @@ TODO: ☐ Ability to include/exclude vods from shuffle in config Ideas: - ☐ Web interface for viewers to issue commands -- twitch extension?!?!?! + ☐ Web interface for viewers to issue commands -- twitch extension?! ☐ Support songrequests -- play through discord? ___________________ Archive: + ✔ Add random chance for room grind playlist to show for certain amount of time @done (18-09-21 12:28) @project(TODO) ✔ show commercials after a video length cap is hit -- show at conclusion of video @done (18-09-19 11:11) @project(TODO) ✔ add memes to commercial scene @done (18-09-19 11:11) @project(TODO) ✔ add $setcurrent support (to update text label through obs websocket instead of chat) @done (18-09-17 18:06) @project(TODO) diff --git a/fgfm.js b/fgfm.js index 660f655..1adb8a5 100755 --- a/fgfm.js +++ b/fgfm.js @@ -4,64 +4,55 @@ // Import modules const irc = require('irc'); -const OBSWebSocket = require('obs-websocket-js'); const schedule = require('node-schedule'); const util = require('./lib/util'); +const GHOBS = require('./lib/ghobs'); // Read internal configuration let config = require('./config.json'); -const snesGames = require('./conf/snesgames.json'); -const twitchChannel = config.twitch.channels[0].toLowerCase(); +config.vods = require(config.vodConfigFile); +let snesGames = require('./conf/snesgames.json'); -let videoQueue = recentlyPlayed = []; -let currentVideo; -let videoTimer; -let lastCommercialShownAt; -let commercialPlaying = false; +// Set up initial state +let state = { + "videoQueue": [], + "recentlyPlayed": [], + "currentVideo": null, + "videoTimer": null, + "lastCommercialShownAt": Date.now(), + "commercialPlaying": false +}; -// Connect to OBS Websocket -const obs = new OBSWebSocket(); -console.log(`Connecting to OBS...`); -obs.connect({ address: config.obs.websocket.address, password: config.obs.websocket.password }) - .then(() => { - console.log(`Success! We're connected to OBS!`); - return twitchInit(config, obs); - }) - .then(twitch => { - return streamInit(config, obs, twitch); - }) - .catch(err => { - console.log(err); - }); - -// Listen for errors from OBS -obs.on('error', err => { - console.error(`OBS socket error: ${JSON.stringify(err)}`); -}); +const obs = new GHOBS(config); +obs.init() + .then(() => {return twitchInit(config.twitch)}) + .then(twitch => {return streamInit(config, twitch)}) + .catch(console.error); // Connect to twitch, set up basic event listeners -const twitchInit = (config, obs) => { +const twitchInit = (config) => { return new Promise((resolve, reject) => { - console.log('Connecting to Twitch...'); + console.log(`Connecting to Twitch / ${config.channel}...`); + let defaultTwitchConfig = { autoRejoin: true, retryCount: 10, - channels: config.twitch.channels, + channels: [config.channel], debug: config.debug }; // Connect to Twitch with the bot account let botChat = new irc.Client( - config.twitch.ircServer, - config.twitch.username, - Object.assign({password: config.twitch.oauth}, defaultTwitchConfig) + config.ircServer, + config.botLogin.username, + Object.assign({password: config.botLogin.oauth}, defaultTwitchConfig) ); // Connect to Twitch with an editor account let editorChat = new irc.Client( - config.twitch.ircServer, - config.twitch.editorLogin.username, - Object.assign({password: config.twitch.editorLogin.oauth}, defaultTwitchConfig) + config.ircServer, + config.editorLogin.username, + Object.assign({password: config.editorLogin.oauth}, defaultTwitchConfig) ); let twitchErrorHandler = message => { @@ -76,127 +67,106 @@ const twitchInit = (config, obs) => { resolve({"botChat": botChat, "editorChat": editorChat}); }); -} +}; // Initialize Stream automation -const streamInit = (config, obs, twitch) => { +// @TODO: Move anything that calls websocket here to GHOBS lib +const streamInit = (config, twitch) => { return new Promise((resolve, reject) => { - videoQueue = config.vods.sort(util.randSort).slice(0, config.initialQueueSize); - console.log(`Initial video queue: ${videoQueue.map((c, i) => `[${i+1}] ${c.chatName}`).join(' | ')}`); - - // Shows a video in the given scene and triggers a callback when it's finished - const playVideoInScene = (video, scene, callback) => { - return new Promise((resolve, reject) => { - // set the file path - obs.setSourceSettings({"sourceName": video.sceneItem, "sourceSettings": {"local_file": video.filePath}}) - // show the video - .then(data => obs.setSceneItemProperties({"item": video.sceneItem, "scene-name": scene, "visible": true})) - // trigger callback when the video is over, but resolve promise immediately with the timer - .then(data => { - resolve(setTimeout(() => {callback(data)}, video.length*1000)); - }) - .catch(reject); - }); - }; + // Set up the initial queue by randomly choosing the configured amount of vods included in shuffling + state.videoQueue = config.vods.alttp.filter(e => e.includeInShuffle === true).sort(util.randSort).slice(0, config.initialQueueSize); + console.log(`Initial video queue: ${state.videoQueue.map((c, i) => `[${i+1}] ${c.chatName}`).join(' | ')}`); // Show a gameplay vod const showVideo = video => { console.log(`Showing video: ${video.chatName}`); - let handleVideoFinish = () => { - obs.setSceneItemProperties({"item": video.sceneItem, "scene-name": config.defaultSceneName, "visible": false}) - .then(data => {nextVideo()}) - .catch(console.error); - }; + // play the next video when the previous finishes + let handleVideoEnd = () => {nextVideo()}; - obs.setCurrentScene({"scene-name": config.defaultSceneName}) - .then(res => { - playVideoInScene(video, config.defaultSceneName, handleVideoFinish) - .then(timer => { - // track timer so we can cancel callback later on if necessary - videoTimer = timer; + obs.playVideoInScene(video, config.defaultSceneName, handleVideoEnd) + .then(timer => { + // track timer so we can cancel callback later on if necessary + state.videoTimer = timer; - // update activity label and show/hide appropriately - if (video.hasOwnProperty('label') && video.label !== false) { - obs.setTextGDIPlusProperties({"source": config.currentActivitySceneItemName, "scene-name": config.defaultSceneName, "render": true, "text": video.label}); - } else { - obs.setSceneItemProperties({"item": config.currentActivitySceneItemName, "scene-name": config.defaultSceneName, "visible": false}); - } - }); + // update activity label and show/hide appropriately + if (video.hasOwnProperty('label') && video.label !== false) { + obs.showActivity(video.label); + } else { + obs.hideActivity(); + } }) .catch(console.error); }; // Picks the next video in the queue (shuffles if empty) - // Also handles "commercial breaks" + // Also handles "commercial breaks" if enabled const nextVideo = () => { // Show a "commercial break" if it's been long enough since the last one - let secondsSinceLastCommercial = (Date.now() - lastCommercialShownAt) / 1000; + /* let secondsSinceLastCommercial = (Date.now() - state.lastCommercialShownAt) / 1000; if (config.commercialsEnabled === true && secondsSinceLastCommercial >= config.commercialInterval) { - commercialPlaying = true; + state.commercialPlaying = true; console.log(`It has been ${secondsSinceLastCommercial} seconds since the last commercial break!`); // Random chance for it to be "everybody wow" if ((Math.floor(Math.random() * 100) + 1) <= config.auwChance) { console.log(`Showing AUW!`); auw(() => { - // show next video in queue - lastCommercialShownAt = Date.now(); - commercialPlaying = false; + // show next video in queue once the commercial is done + state.lastCommercialShownAt = Date.now(); + state.commercialPlaying = false; nextVideo(); }); } else { - let commercial = config.memes.sort(util.randSort)[0]; + let commercial = config.vods.memes.sort(util.randSort)[0]; console.log(`Showing random meme: ${commercial.name}`); - obs.setCurrentScene({"scene-name": config.commercialSceneName}) - .then(res => { - return playVideoInScene(commercial, config.commercialSceneName, () => { - // hide video - obs.setSceneItemProperties({"item": commercial.sceneItem, "scene-name": config.commercialSceneName, "visible": false}) - // unmute songrequest audio - twitch.editorChat.say(twitchChannel, `!volume ${config.defaultSRVolume}`); - // show next video in queue - lastCommercialShownAt = Date.now(); - commercialPlaying = false; - nextVideo(); - }) - }) + let handleCommercialFinish = () => { + // unmute songrequest audio + twitch.editorChat.say(config.twitch.channel, `!volume ${config.defaultSRVolume}`); + + // update commercial state and show next video in queue + state.lastCommercialShownAt = Date.now(); + state.commercialPlaying = false; + nextVideo(); + }; + + obs.playVideoInScene(commercial, config.commercialSceneName, handleCommercialFinish) .then(res => { // mute songrequest audio - twitch.editorChat.say(twitchChannel, '!volume 0'); - }) - .catch(console.error); + twitch.editorChat.say(config.twitch.channel, `!volume 0`); + }); } return; - } + }*/ // Keep track of recently played videos - if (recentlyPlayed.length === config.recentlyPlayedMemory) { - recentlyPlayed.shift(); + if (state.recentlyPlayed.length === config.recentlyPlayedMemory) { + state.recentlyPlayed.shift(); } - recentlyPlayed.push(currentVideo.id); + state.recentlyPlayed.push(state.currentVideo.id); - // if a commercial/meme is playing (manually triggered), wait until it's done and calls this function again - if (commercialPlaying === true) { - return; + // If a commercial is playing, wait until it's done + while (state.commercialPlaying === true) { + // } // play the next video in the queue, or pick one at random if the queue is empty - if (videoQueue.length > 0) { - currentVideo = videoQueue.shift(); + if (state.videoQueue.length > 0) { + state.currentVideo = state.videoQueue.shift(); } else { // Random chance for room grind to be played for an amount of time instead of another video be shuffled to if ((Math.floor(Math.random() * 100) + 1) <= config.roomGrindChance) { console.log(`Room grind selected!`); // show room-grind source - obs.setSceneItemProperties({"item": "room-grind", "scene-name": config.defaultSceneName, "visible": true}) + // obs.showRoomGrind(config.roomGrindPlaytime); + obs.websocket.setSceneItemProperties({"item": "room-grind", "scene-name": config.defaultSceneName, "visible": true}) .then(res => { - obs.setTextGDIPlusProperties({"source": config.currentActivitySceneItemName, "scene-name": config.defaultSceneName, "render": true, "text": "NOW SHOWING: TTAS Room Grind !ttas"}); - videoTimer = setTimeout(() => { + obs.showActivity("NOW SHOWING: TTAS Room Grind !ttas"); + state.videoTimer = setTimeout(() => { // after timeout, hide room-grind and call nextVideo() - obs.setSceneItemProperties({"item": "room-grind", "scene-name": config.defaultSceneName, "visible": false}); + obs.websocket.setSceneItemProperties({"item": "room-grind", "scene-name": config.defaultSceneName, "visible": false}); nextVideo(); }, config.roomGrindPlaytime*1000) }); @@ -205,53 +175,54 @@ const streamInit = (config, obs, twitch) => { } // filter recently played from shuffle - let freshVods = config.vods.filter(e => { - return !recentlyPlayed.includes(e.id); + let freshVods = config.vods.alttp.filter(e => { + return e.includeInShuffle === true && !state.recentlyPlayed.includes(e.id); }); - currentVideo = freshVods.sort(util.randSort).slice(0, 1).shift(); + state.currentVideo = freshVods.sort(util.randSort).slice(0, 1).shift(); } - showVideo(currentVideo); + showVideo(state.currentVideo); }; - lastCommercialShownAt = Date.now(); + // Start queue playback + state.currentVideo = state.videoQueue.shift(); + showVideo(state.currentVideo); - // grab the first video in the queue and show it - currentVideo = videoQueue.shift(); - showVideo(currentVideo); + const showCommercial = (video, callback) => { + return new Promise((resolve, reject) => { + let handleFinish = () => { + // unmute songrequest audio + //twitch.editorChat.say(config.twitch.channel, `!volume ${config.defaultSRVolume}`); + if (typeof callback !== 'undefined') callback(); + }; + obs.playVideoInScene(video, config.commercialSceneName, handleFinish) + .then(timer => { + // mute songrequest audio + //twitch.editorChat.say(config.twitch.channel, `!volume 0`); + resolve(timer); + }) + .catch(reject); + }); + }; + + // Everybody OwenWow const auw = (callback) => { - let currentScene; - obs.getCurrentScene() - .then(res => { - currentScene = res.name; - // switch to commercial scene - return obs.setCurrentScene({"scene-name": config.commercialSceneName}); - }) - .then(res => { - // show the video - return obs.setSceneItemProperties({"item": "everybody-wow", "scene-name": config.commercialSceneName, "visible": true}); - }) - .then(res => { - // mute songrequest audio - twitch.editorChat.say(twitchChannel, '!volume 0'); + // find the vod in memes + let video = config.vods.memes.find(e => e.id === 'auw'); + let handleFinish = () => { + // hide owen + obs.websocket.setSceneItemProperties({"item": "owen", "scene-name": config.commercialSceneName, "visible": false}); + // trigger user callback + if (typeof callback !== 'undefined') callback(); + }; + + showCommercial(video, handleFinish) + .then(videoHasStarted => { // show owen - obs.setSceneItemProperties({"item": "owen", "scene-name": config.commercialSceneName, "visible": true}); + obs.websocket.setSceneItemProperties({"item": "owen", "scene-name": config.commercialSceneName, "visible": true}); // tell chat what's up - twitch.botChat.say(twitchChannel, 'Everybody OwenWow'); - // swap back to the original scene after the video ends - setTimeout(() => { - // hide video - obs.setSceneItemProperties({"item": "everybody-wow", "scene-name": config.commercialSceneName, "visible": false}) - // hide owen - obs.setSceneItemProperties({"item": "owen", "scene-name": config.commercialSceneName, "visible": false}); - // unmute songrequest audio - twitch.editorChat.say(twitchChannel, `!volume ${config.defaultSRVolume}`); - // swap back to fgfm - obs.setCurrentScene({"scene-name": currentScene}); - // trigger user callback - if (callback) callback(); - }, 246500); + twitch.botChat.say(config.twitch.channel, 'Everybody OwenWow'); }) .catch(console.error); }; @@ -289,13 +260,13 @@ const streamInit = (config, obs, twitch) => { sceneItem["scene-name"] = sceneOrGroup; } - obs.getSceneItemProperties(sceneItem) + obs.websocket.getSceneItemProperties(sceneItem) .then(data => { if (data.visible === newVisibility) { twitch.botChat.say(to, `This scene item is already ${visibleTerm}. DerpHam`); } else { sceneItem.visible = newVisibility; - obs.setSceneItemProperties(sceneItem) + obs.websocket.setSceneItemProperties(sceneItem) .then(res => { twitch.botChat.say(to, `${target} is now ${visibleTerm}.`); }) @@ -305,9 +276,7 @@ const streamInit = (config, obs, twitch) => { .catch(err => { twitch.botChat.say(to, JSON.stringify(err)); }); - - - + // TOGGLE SOURCE VISIBILITY } else if (commandNoPrefix === 't') { let target = commandParts[1] || false; @@ -318,13 +287,13 @@ const streamInit = (config, obs, twitch) => { let sceneItem = {"item": target}; - obs.getSceneItemProperties(sceneItem) + obs.websocket.getSceneItemProperties(sceneItem) .then(data => { let newVisibility = !data.visible; let visibleTerm = (newVisibility ? 'visible' : 'hidden'); sceneItem.visible = newVisibility; - obs.setSceneItemProperties(sceneItem) + obs.websocket.setSceneItemProperties(sceneItem) .then(res => { twitch.botChat.say(to, `${target} is now ${visibleTerm}.`); }) @@ -334,7 +303,6 @@ const streamInit = (config, obs, twitch) => { twitch.botChat.say(to, JSON.stringify(err)); }); - // SWAP -- Hide one source, show another } else if (commandNoPrefix === 'swap') { // hide first argument, show second argument @@ -345,42 +313,28 @@ const streamInit = (config, obs, twitch) => { return } - obs.setSceneItemProperties({"item": targetToHide, "visible": false}) + obs.websocket.setSceneItemProperties({"item": targetToHide, "visible": false}) .then(res => { - obs.setSceneItemProperties({"item": targetToShow, "visible": true}); + obs.websocket.setSceneItemProperties({"item": targetToShow, "visible": true}); }) .catch(console.error); - - + // Black Box "Everybody Wow" } else if (commandNoPrefix === 'auw') { - commercialPlaying = true; + state.commercialPlaying = true; auw(() => { - commercialPlaying = false; + state.commercialPlaying = false; }); + // memes on-demand } else if (commandNoPrefix === 'meme') { - commercialPlaying = true; - let commercial = config.memes.sort(util.randSort)[0]; - obs.setCurrentScene({"scene-name": config.commercialSceneName}) - .then(res => { - return playVideoInScene(commercial, config.commercialSceneName, () => { - // video is done playing, hide it - obs.setSceneItemProperties({"item": commercial.sceneItem, "scene-name": config.commercialSceneName, "visible": false}) - // unmute songrequest audio - twitch.editorChat.say(to, `!volume ${config.defaultSRVolume}`); - // swap back to fgfm - obs.setCurrentScene({"scene-name": config.defaultSceneName}); - commercialPlaying = false; - }); - }) - .then(res => { - // mute songrequest audio once video starts playing - twitch.editorChat.say(to, '!volume 0'); - }) - .catch(console.error); + // @TODO: support request by ID + state.commercialPlaying = true; + let commercial = config.vods.memes.sort(util.randSort)[0]; + showCommercial(commercial, () => { + state.commercialPlaying = false; + }); - // SWITCH SCENES } else if (commandNoPrefix === 'switch') { @@ -390,19 +344,18 @@ const streamInit = (config, obs, twitch) => { return; } - obs.getCurrentScene() + obs.websocket.getCurrentScene() .then(data => { if (data.name === target) { twitch.botChat.say(to, `That scene is already active! DerpHam`); } else { - obs.setCurrentScene({"scene-name": target}) + obs.websocket.setCurrentScene({"scene-name": target}) .then(() => {twitch.botChat.say(to, `${target} is now active`)}) .catch(console.error); } }) .catch(console.error); - // SET ON-SCREEN ACTIVITY } else if (commandNoPrefix === 'setactivity') { let target = commandParts.slice(1).join(' '); @@ -411,30 +364,27 @@ const streamInit = (config, obs, twitch) => { return; } - obs.setTextGDIPlusProperties({"source": config.currentActivitySceneItemName, "scene-name": config.defaultSceneName, "render": true, "text": target}) + obs.websocket.setTextGDIPlusProperties({"source": config.currentActivitySceneItemName, "scene-name": config.defaultSceneName, "render": true, "text": target}) .then(res => { twitch.botChat.say(to, `Activity updated!`); return; }) .catch(console.error); - - + // REBOOT } else if (commandNoPrefix === 'reboot') { console.log('Received request from admin to reboot...'); twitch.botChat.say(to, 'Rebooting...'); process.exit(0); - // SKIP } else if (commandNoPrefix === 'skip') { - clearTimeout(videoTimer); - obs.setSceneItemProperties({"item": currentVideo.sceneItem, "scene-name": config.defaultSceneName, "visible": false}) + clearTimeout(state.videoTimer); + obs.websocket.setSceneItemProperties({"item": state.currentVideo.sceneItem, "scene-name": config.defaultSceneName, "visible": false}) .then(res => { nextVideo(); }); - // ADD } else if (commandNoPrefix === 'add') { let requestedVideoId = commandParts[1] || false; @@ -444,30 +394,28 @@ const streamInit = (config, obs, twitch) => { } // make sure request vid isn't in the queue already - if (videoQueue.findIndex(e => e.id == requestedVideoId) !== -1) { + if (state.videoQueue.findIndex(e => e.id == requestedVideoId) !== -1) { twitch.botChat.say(to, `That video is in the queue already!`); return; } - // search for req'd vid by id in config.vods - let vodIndex = config.vods.findIndex(e => e.id == requestedVideoId); + // search for req'd vid by id in config.vods.alttp + let vodIndex = config.vods.alttp.findIndex(e => e.id == requestedVideoId); if (vodIndex === -1) { twitch.botChat.say(to, `A video with that ID does not exist!`); return; } // add to queue if it exists - videoQueue.push(config.vods[vodIndex]); - twitch.botChat.say(to, `${config.vods[vodIndex].chatName} has been added to the queue [${videoQueue.length}]`); + state.videoQueue.push(config.vods.alttp[vodIndex]); + twitch.botChat.say(to, `${config.vods.alttp[vodIndex].chatName} has been added to the queue [${state.videoQueue.length}]`); return; - // START VOTE } else if (commandNoPrefix === 'startvote') { videoVoteJob.reschedule("*/15 * * * *"); twitch.botChat.say(to, `Video Queue Voting will start in 15 minutes!`); - // PAUSE VOTE } else if (commandNoPrefix === 'pausevote') { clearInterval(rtvInterval); @@ -514,8 +462,8 @@ const streamInit = (config, obs, twitch) => { // QUEUE STATUS } else if (commandNoPrefix === 'queue') { - if (videoQueue.length > 0) { - let chatQueue = videoQueue.map((c, i) => { + if (state.videoQueue.length > 0) { + let chatQueue = state.videoQueue.map((c, i) => { return `[${i+1}] ${c.chatName}`; }); twitch.botChat.say(to, chatQueue.join(' | ')); @@ -526,13 +474,13 @@ const streamInit = (config, obs, twitch) => { // CURRENT VIDEO } else if (commandNoPrefix === 'current') { - twitch.botChat.say(to, `Now Playing: ${currentVideo.chatName}`); + twitch.botChat.say(to, `Now Playing: ${state.currentVideo.chatName}`); // NEXT VIDEO } else if (commandNoPrefix === 'next') { - if (videoQueue.length > 0) { - twitch.botChat.say(to, `Next Video: ${videoQueue[0].chatName}`); + if (state.videoQueue.length > 0) { + twitch.botChat.say(to, `Next Video: ${state.videoQueue[0].chatName}`); } else { twitch.botChat.say(to, `No videos currently in queue!`); } @@ -547,21 +495,21 @@ const streamInit = (config, obs, twitch) => { } // make sure request vid isn't in the queue already - if (videoQueue.findIndex(e => e.id === requestedVideoId) !== -1) { + if (state.videoQueue.findIndex(e => e.id === requestedVideoId) !== -1) { twitch.botChat.say(to, `That video is in the queue already!`); return; } - // search for req'd vid by id in config.vods - let vodIndex = config.vods.findIndex(e => e.id === requestedVideoId); + // search for req'd vid by id in config.vods.alttp + let vodIndex = config.vods.alttp.findIndex(e => e.id === requestedVideoId); if (vodIndex === -1) { twitch.botChat.say(to, `A video with that ID does not exist!`); return; } // add to queue if it exists - videoQueue.push(config.vods[vodIndex]); - twitch.botChat.say(to, `${config.vods[vodIndex].chatName} has been added to the queue [${videoQueue.length}]`); + state.videoQueue.push(config.vods.alttp[vodIndex]); + twitch.botChat.say(to, `${config.vods.alttp[vodIndex].chatName} has been added to the queue [${state.videoQueue.length}]`); return; // RNGAMES @@ -571,13 +519,12 @@ const streamInit = (config, obs, twitch) => { //////////////// } }); - - console.log(`Initializing stream timers...`); + // @TODO: Modularize timed events + console.log(`Initializing stream timers...`); let userVotes = currentChoices = []; let rockTheVote = () => {}; let rtvInterval = setInterval(() => {rockTheVote()}, 300000); - let videoVoteJob = new schedule.Job(async () => { // Tally votes from previous election (if there was one), add the winner to the queue let winner; @@ -586,7 +533,7 @@ const streamInit = (config, obs, twitch) => { // choose a random element from currentChoices winner = util.randElement(currentChoices); console.log(`VIDEO CHOSEN RANDOMLY: ${winner.chatName}`); - twitch.botChat.say(twitchChannel, `No Votes Logged -- Next Video Chosen at Random: ${winner.chatName}`); + twitch.botChat.say(config.twitch.channel, `No Votes Logged -- Next Video Chosen at Random: ${winner.chatName}`); } else { // tally and sort votes let voteTallies = []; @@ -612,18 +559,18 @@ const streamInit = (config, obs, twitch) => { console.log(`Voting Results: ${JSON.stringify(voteTallies)}`); winner = currentChoices[voteTallies[0].id-1]; console.log(`WINNER OF THE VOTE: ${winner.chatName}`); - twitch.botChat.say(twitchChannel, `Winner of the Video Vote: ${winner.chatName}`); + twitch.botChat.say(config.twitch.channel, `Winner of the Video Vote: ${winner.chatName}`); // clear user votes userVotes = []; } - videoQueue.push(winner); + state.videoQueue.push(winner); } - // choose more random videos from config.vods (that aren't already in the queue) - let vodsNotInQueue = config.vods.filter(e => { - let inQueue = videoQueue.findIndex(q => q.id === e.id) !== -1; + // choose more random videos from config.vods.alttp (that aren't already in the queue) + let vodsNotInQueue = config.vods.alttp.filter(e => { + let inQueue = state.videoQueue.findIndex(q => q.id === e.id) !== -1; return !inQueue; }); currentChoices = vodsNotInQueue.sort(util.randSort).slice(0, config.videoPollSize); @@ -634,16 +581,16 @@ const streamInit = (config, obs, twitch) => { }); rockTheVote = () => { - twitch.botChat.say(twitchChannel, `Vote for which video you'd like to add to the queue using ${config.twitch.cmdPrefix}vote #: ${chatChoices.join(' | ')}`) + twitch.botChat.say(config.twitch.channel, `Vote for which video you'd like to add to the queue using ${config.twitch.cmdPrefix}vote #: ${chatChoices.join(' | ')}`) }; clearInterval(rtvInterval); rockTheVote(); rtvInterval = setInterval(() => {rockTheVote()}, 300000); }); - resolve(); + resolve(obs); }); -} +}; // catches Promise errors process.on('unhandledRejection', console.error); diff --git a/lib/ghobs.js b/lib/ghobs.js new file mode 100755 index 0000000..72bcbed --- /dev/null +++ b/lib/ghobs.js @@ -0,0 +1,70 @@ +const OBSWebSocket = require('obs-websocket-js'); + +function GHOBS(config) { + this.config = config; + this.websocket = new OBSWebSocket(); + + this.init = () => { + return new Promise((resolve, reject) => { + console.log(`Connecting to OBS Websocket...`); + this.websocket.connect({ address: this.config.obs.websocket.address, password: this.config.obs.websocket.password }) + .then(() => { + console.log(`Success! We're connected to OBS!`); + this.websocket.getCurrentScene().then(res => this.currentScene = res.name); + this.websocket.onSwitchScenes(data => this.currentScene = data['scene-name']); + resolve(); + }) + .catch(reject); + + // Listen for errors from OBS + this.websocket.on('error', err => { + console.error(`OBS websocket error: ${JSON.stringify(err)}`); + }); + }); + }; + + // @TODO: pass any unrecognized commands to the websocket + + // Shows a video in the given scene/item and then hides it and switches back to the original scene when finished + this.playVideoInScene = (video, scene, callback) => { + return new Promise((resolve, reject) => { + let originalScene = this.currentScene; + this.websocket.setCurrentScene({"scene-name": scene}) + .then(res => { + // set the file path on the source + this.websocket.setSourceSettings({"sourceName": video.sceneItem, "sourceSettings": {"local_file": video.filePath}}) + // show the video scene item + .then(data => this.websocket.setSceneItemProperties({"item": video.sceneItem, "scene-name": scene, "visible": true})) + // when the video is over, hide it and trigger the user callback, but resolve promise immediately with the timer + .then(data => { + resolve(setTimeout(() => { + this.websocket.setSceneItemProperties({"item": video.sceneItem, "scene-name": scene, "visible": false}); + this.websocket.setCurrentScene({"scene-name": originalScene}); + callback(data); + }, video.length*1000)) + }); + }) + .catch(reject); + }); + }; + + this.showActivity = (newActivity) => { + let update = { + "source": this.config.currentActivitySceneItemName, + "scene-name": this.config.defaultSceneName, + "render": true + }; + + if (typeof newActivity !== 'undefined' && newActivity.length > 0) { + update.text = newActivity; + } + + return this.websocket.setTextGDIPlusProperties(update); + }; + + this.hideActivity = () => { + return this.websocket.setSceneItemProperties({"item": this.config.currentActivitySceneItemName, "scene-name": this.config.defaultSceneName, "visible": false}); + }; +}; + +module.exports = GHOBS;