ppt_control / ppt_control_obs.pyon commit bump version (e5c6ba6)
   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"