Last active 1738084642

Use this script to sync streamlabs scene switches to Voicemeeter states.

vm_streamlabs_sync.py Raw
1import asyncio
2import logging
3from collections import namedtuple
4
5import voicemeeterlib
6from pyslobs import ScenesService, config_from_ini_else_stdin, connection
7
8
9class Observer:
10 """
11 Observer class to handle scene switch events and perform corresponding actions.
12 Attributes:
13 _vm: An instance of the virtual mixer.
14 conn: Connection object to communicate with the Streamlabs ScenesService.
15 Methods:
16 __init__(vm, conn):
17 Initializes the Observer with a virtual mixer and streamlabs connection.
18 subscribe():
19 Subscribes to the scene switch event from the ScenesService.
20 on_switch_scene(_, message):
21 Handles the scene switch event and calls the appropriate method based on the scene name.
22 on_start():
23 Handles actions to be performed when the scene switches to 'START_TEST'.
24 on_brb():
25 Handles actions to be performed when the scene switches to 'BRB_TEST'.
26 on_end():
27 Handles actions to be performed when the scene switches to 'END_TEST'.
28 on_live():
29 Handles actions to be performed when the scene switches to 'LIVE_TEST'.
30 """
31
32 def __init__(self, vm, conn):
33 self._vm = vm
34 self.conn = conn
35
36 async def subscribe(self):
37 await ScenesService(self.conn).scene_switched.subscribe(self.on_switch_scene)
38
39 async def on_switch_scene(self, _, message):
40 scene = message.get('name')
41 print(f'Switching to {scene}')
42 match scene:
43 case 'START_TEST':
44 await self.on_start()
45 case 'BRB_TEST':
46 await self.on_brb()
47 case 'END_TEST':
48 await self.on_end()
49 case 'LIVE_TEST':
50 await self.on_live()
51 case _:
52 pass
53
54 async def on_start(self):
55 self._vm.strip[0].mute = True
56 self._vm.strip[1].B1 = True
57 self._vm.strip[2].B2 = True
58
59 async def on_brb(self):
60 self._vm.strip[7].fadeto(0, 500)
61 self._vm.bus[0].mute = True
62
63 async def on_end(self):
64 self._vm.apply(
65 {
66 'strip-0': {'mute': True},
67 'strip-1': {'mute': True, 'B1': False},
68 'strip-2': {'mute': True, 'B1': False},
69 'vban-in-0': {'on': False},
70 }
71 )
72
73 async def on_live(self):
74 self._vm.strip[0].mute = False
75 self._vm.strip[7].fadeto(-6, 500)
76 self._vm.strip[7].A3 = True
77 self._vm.vban.instream[0].on = True
78
79
80async def rotate_scenes(conn):
81 """
82 Rotates through specific scenes and makes each one active for a short duration.
83 Args:
84 conn: The connection object to the ScenesService.
85 This function retrieves all scenes using the ScenesService, then iterates through
86 a predefined list of scene names ('START_TEST', 'BRB_TEST', 'END_TEST', 'LIVE_TEST').
87 For each matching scene, it makes the scene active and waits for 1 second before
88 moving to the next scene. Finally, it closes the connection.
89 """
90
91 Scene = namedtuple('Scene', 'name id')
92
93 ss = ScenesService(conn)
94 scenes = await ss.get_scenes()
95 for scene in (Scene(s.name, s.id) for s in scenes):
96 if scene.name in ('START_TEST', 'BRB_TEST', 'END_TEST', 'LIVE_TEST'):
97 await ss.make_scene_active(scene.id)
98 await asyncio.sleep(1)
99 conn.close()
100
101
102async def main():
103 """
104 Main asynchronous function to initialize and run the application.
105 This function performs the following tasks:
106 1. Establishes a connection to the Voicemeeter API using the 'potato' version.
107 2. Creates a connection to Streamlabs using configuration from an INI file or standard input.
108 3. Initializes an observer to monitor and react to events from Voicemeeter and Streamlabs.
109 4. Runs background processing tasks concurrently, including:
110 - Streamlabs connection background processing
111 - Observer subscription to events
112 - Streamlabs scene rotation
113 The function uses asyncio.gather to run these tasks concurrently.
114 Note:
115 This function should be run within an asyncio event loop.
116 """
117
118 with voicemeeterlib.api('potato') as vm:
119 conn = connection.SlobsConnection(config_from_ini_else_stdin())
120 await asyncio.gather(
121 conn.background_processing(),
122 Observer(vm, conn).subscribe(),
123 rotate_scenes(conn),
124 )
125
126
127if __name__ == '__main__':
128 logging.basicConfig(level=logging.INFO)
129
130 asyncio.run(main())
131