1# -*- coding: utf-8 -*-
2
3import obspython as obs
4import asyncio
5import websockets
6import threading
7import logging, logging.handlers
8from time import sleep
9
10PORT_DEFAULT = 5678
11HOSTNAME_DEFAULT = "localhost"
12
13hotkey_id_first = None
14hotkey_id_prev = None
15hotkey_id_next = None
16hotkey_id_last = None
17hotkey_id_black = None
18hotkey_id_white = None
19
20HOTKEY_NAME_FIRST = 'powerpoint_slides.first'
21HOTKEY_NAME_PREV = 'powerpoint_slides.previous'
22HOTKEY_NAME_NEXT = 'powerpoint_slides.next'
23HOTKEY_NAME_LAST = 'powerpoint_slides.last'
24HOTKEY_NAME_BLACK = 'powerpoint_slides.black'
25HOTKEY_NAME_WHITE = 'powerpoint_slides.white'
26
27HOTKEY_DESC_FIRST = 'First PowerPoint slide'
28HOTKEY_DESC_PREV = 'Previous PowerPoint slide'
29HOTKEY_DESC_NEXT = 'Next PowerPoint slide'
30HOTKEY_DESC_LAST = 'Last PowerPoint slide'
31HOTKEY_DESC_BLACK = 'Black PowerPoint slide'
32HOTKEY_DESC_WHITE = 'White PowerPoint slide'
33
34LOG_LEVEL = logging.WARNING
35
36cmd = ""
37hostname = HOSTNAME_DEFAULT
38port = PORT_DEFAULT
39attempts = 0
40logger = None
41slideshow = ""
42
43logging.basicConfig()
44
45async def communicate():
46 async with websockets.connect("ws://%s:%s" % (hostname, port), ping_interval=None) as websocket:
47 global cmd
48 global attempts
49 while True:
50 if cmd:
51 try:
52 cmd_temp = cmd
53 cmd = ""
54 await websocket.send('{"presentation": "", "action": "%s"}' % cmd_temp)
55 except websockets.ConnectionClosed as exc:
56 logger.info("Failed to send command {}: {}".format(cmd_temp, str(exc)))
57 cmd = cmd_temp
58 attempts += 1
59 if attempts == 4:
60 logger.info("Failed to send command {} after {} attempts - aborting connection".format(cmd_temp, attempts))
61 attempts = 0
62 cmd = ""
63 break
64 await asyncio.sleep(0.05 + 0.5*attempts**2)
65
66def run_ws():
67 while True:
68 logger.debug("Attempting to connect")
69 try:
70 asyncio.set_event_loop(asyncio.new_event_loop())
71 asyncio.get_event_loop().run_until_complete(communicate())
72 except (OSError, websockets.exceptions.ConnectionClosedError) as e:
73 # No server available - just keep trying
74 pass
75 except Exception as e:
76 logger.warning("Failed to connect to websocket: {} - {}".format(type(e), e))
77 finally:
78 sleep(1)
79
80#------------------------------------------------------------
81# global functions for script plugins
82
83def script_load(settings):
84 global hotkey_id_first
85 global hotkey_id_prev
86 global hotkey_id_next
87 global hotkey_id_last
88 global hotkey_id_black
89 global hotkey_id_white
90 global logger
91
92 hotkey_id_first = register_and_load_hotkey(settings, HOTKEY_NAME_FIRST, HOTKEY_DESC_FIRST, first_slide)
93 hotkey_id_prev = register_and_load_hotkey(settings, HOTKEY_NAME_PREV, HOTKEY_DESC_PREV, prev_slide)
94 hotkey_id_next = register_and_load_hotkey(settings, HOTKEY_NAME_NEXT, HOTKEY_DESC_NEXT, next_slide)
95 hotkey_id_last = register_and_load_hotkey(settings, HOTKEY_NAME_LAST, HOTKEY_DESC_LAST, last_slide)
96 hotkey_id_black = register_and_load_hotkey(settings, HOTKEY_NAME_BLACK, HOTKEY_DESC_BLACK, black)
97 hotkey_id_white = register_and_load_hotkey(settings, HOTKEY_NAME_WHITE, HOTKEY_DESC_WHITE, white)
98
99 # Set up logging
100 log_formatter = logging.Formatter("%(asctime)s [%(threadName)-12.12s] [%(levelname)-7.7s] %(message)s")
101 logger = logging.getLogger("ppt_control_obs")
102 logger.setLevel(LOG_LEVEL)
103 logger.propagate = False
104 console_handler = logging.StreamHandler()
105 console_handler.setFormatter(log_formatter)
106 console_handler.setLevel(LOG_LEVEL)
107 logger.addHandler(console_handler)
108
109 ws_daemon = threading.Thread(name="ws_daemon", target=run_ws)
110 ws_daemon.setDaemon(True)
111 ws_daemon.start()
112 logger.info("Started websocket client")
113
114def script_unload():
115 obs.obs_hotkey_unregister(first_slide)
116 obs.obs_hotkey_unregister(prev_slide)
117 obs.obs_hotkey_unregister(next_slide)
118 obs.obs_hotkey_unregister(last_slide)
119 obs.obs_hotkey_unregister(black)
120 obs.obs_hotkey_unregister(white)
121
122def script_save(settings):
123 save_hotkey(settings, HOTKEY_NAME_FIRST, hotkey_id_first)
124 save_hotkey(settings, HOTKEY_NAME_PREV, hotkey_id_prev)
125 save_hotkey(settings, HOTKEY_NAME_NEXT, hotkey_id_next)
126 save_hotkey(settings, HOTKEY_NAME_LAST, hotkey_id_last)
127 save_hotkey(settings, HOTKEY_NAME_BLACK, hotkey_id_black)
128 save_hotkey(settings, HOTKEY_NAME_WHITE, hotkey_id_white)
129
130def script_description():
131 return """ppt-control client
132
133 Provides hotkeys for controlling PowerPoint slides using websockets.
134 Go to OBS settings -> Hotkeys to change hotkeys (none set by default)."""
135
136def script_defaults(settings):
137 obs.obs_data_set_default_string(settings, 'hostname', HOSTNAME_DEFAULT)
138 obs.obs_data_set_default_int(settings, 'port', PORT_DEFAULT)
139
140def script_properties():
141 props = obs.obs_properties_create()
142
143 obs.obs_properties_add_text(props, "hostname", "Hostname: ", obs.OBS_TEXT_DEFAULT)
144 obs.obs_properties_add_int(props, "port", "Port: ", 0, 9999, 1)
145 return props
146
147def script_update(settings):
148 global port
149 port = obs.obs_data_get_int(settings, "port")
150 hostname = obs.obs_data_get_string(settings, "hostname")
151
152def register_and_load_hotkey(settings, name, description, callback):
153 hotkey_id = obs.obs_hotkey_register_frontend(name, description, callback)
154 hotkey_save_array = obs.obs_data_get_array(settings, name)
155 obs.obs_hotkey_load(hotkey_id, hotkey_save_array)
156 obs.obs_data_array_release(hotkey_save_array)
157
158 return hotkey_id
159
160def save_hotkey(settings, name, hotkey_id):
161 hotkey_save_array = obs.obs_hotkey_save(hotkey_id)
162 obs.obs_data_set_array(settings, name, hotkey_save_array)
163 obs.obs_data_array_release(hotkey_save_array)
164
165#-------------------------------------
166
167def first_slide(pressed):
168 if pressed:
169 global cmd
170 cmd = "first"
171
172def prev_slide(pressed):
173 if pressed:
174 global cmd
175 cmd = "prev"
176
177def next_slide(pressed):
178 if pressed:
179 global cmd
180 cmd = "next"
181
182def last_slide(pressed):
183 if pressed:
184 global cmd
185 cmd = "last"
186
187def black(pressed):
188 if pressed:
189 global cmd
190 cmd = "black"
191
192def white(pressed):
193 if pressed:
194 global cmd
195 cmd = "white"