Commit 9ca09ce5 authored by nextime's avatar nextime

Internal communication for OBS events

parent 9b7a4469
...@@ -34,9 +34,12 @@ config = configparser.ConfigParser() ...@@ -34,9 +34,12 @@ config = configparser.ConfigParser()
config.read('shmcamstudio.conf') config.read('shmcamstudio.conf')
qcore = queue.Queue() # Inter thread communication
qcore = queue.Queue() # IN -> core application (studio.py)
qobs = queue.Queue() # IN -> OBS module
builtins.qcore = qcore builtins.qcore = qcore
builtins.qobs = qobs
builtins.config = config builtins.config = config
# Setup logging # Setup logging
......
...@@ -13,11 +13,22 @@ button_width = 20 ...@@ -13,11 +13,22 @@ button_width = 20
button_height = 2 button_height = 2
font_size = 12 font_size = 12
[OBS:mdma] #[OBS:mdma]
host = 192.168.42.115 #host = 192.168.42.115
#port = 4455
#password = motorol4
[OBS:slut]
host = 192.168.42.125
port = 4455 port = 4455
password = motorol4 password = motorol4
[OBS:leeloo]
host = 192.168.42.111
port = 4455
password = motorol4
# OUTPUT are shenes on OBS sending to a specific stream, can be a rtmp stream with # OUTPUT are shenes on OBS sending to a specific stream, can be a rtmp stream with
# OBS multistream plugin or a virtual cam, both the included in OBS or the ones # OBS multistream plugin or a virtual cam, both the included in OBS or the ones
...@@ -26,23 +37,25 @@ password = motorol4 ...@@ -26,23 +37,25 @@ password = motorol4
[OUTPUT:smleeloo] [OUTPUT:smleeloo]
obs = leeloo obs = leeloo
scene = LIVE SFW scene = LIVE SFW
source_title = 5 status.open.disable = 5,4,3
source_closed = 4 status.close.disable = 3
source_tease = 3 status.close.enable = 5,4
status.tease.disable = 4
status.tease.enable = 5,3
[OUTPUT:smstefy] [OUTPUT:smstefy]
obs = slut obs = slut
scene = live SFW scene = live SFW
source_title = 5 source.title = 5
source_closed = 4 source.closed = 4
source_tease = 3 source.tease = 3
[OUTPUT:shine] [OUTPUT:shine]
obs = slut obs = slut
scene = SHINE scene = SHINE
source_title = 4 source.title = 4
source_closed = 2 source.closed = 2
source_tease = 3 source.tease = 3
#[OUTPUT:livejasmin] #[OUTPUT:livejasmin]
#obs = slut #obs = slut
...@@ -83,17 +96,29 @@ color = green ...@@ -83,17 +96,29 @@ color = green
[BUTTON:2:shine_openclose] [BUTTON:2:shine_openclose]
title = Open/Close Shine title = Open/Close Shine
action = shine_openclose action = shine_openclose
color = orange output = shine
color.close = red
color.open = green
color.tease = orange
color = grey
[BUTTON:2:stefy_openclose] [BUTTON:2:stefy_openclose]
title = Open/Close Stefy title = Open/Close Stefy
action = stefy_openclose action = stefy_openclose
color = orange output = smstefy
color.close = red
color.open = green
color.tease = orange
color = grey
[BUTTON:2:leelo_openclose] [BUTTON:2:leelo_openclose]
title = Open/Close Leeloo title = Open/Close Leeloo
action = leelo_openclose action = leelo_openclose
color = orange output = smleeloo
color.close = red
color.open = green
color.tease = orange
color = grey
#[BUTTON:2:leelo_livejasmine] #[BUTTON:2:leelo_livejasmine]
#title = Open/Close JASM #title = Open/Close JASM
...@@ -142,7 +167,7 @@ execute = /usr/local/bin/smblur_private_leeloo ...@@ -142,7 +167,7 @@ execute = /usr/local/bin/smblur_private_leeloo
#execute = /usr/local/bin/smblur_private_jasmin #execute = /usr/local/bin/smblur_private_jasmin
[ACTION:leelo_openclose] [ACTION:leelo_openclose]
execute = /usr/local/bin/smblur_leelo execute = /usr/local/bin/smblur_leeloo
[ACTION:shine_openclose] [ACTION:shine_openclose]
execute = /usr/local/bin/smblur_shine execute = /usr/local/bin/smblur_shine
......
...@@ -15,7 +15,92 @@ ...@@ -15,7 +15,92 @@
import obsws_python as obs import obsws_python as obs
import time import time
from contextlib import suppress
import websocket
import queue
import logging
obws_logger = logging.getLogger("obsws_python.baseclient")
obws_logger.setLevel(logging.CRITICAL)
logger = logging.getLogger(__name__)
class OBSOutput:
def __init__(self, output, config_section, obss):
self.config_section = config_section
self.output = output
self.status = False
self.obss = obss
self.config_options = config.options(config_section)
self.config_section = config_section
self.statuses = {}
self.inputs = {}
for c in self.config_options:
if 'status.' in c:
for inp in config.get(config_section, c).split(','):
if inp not in self.inputs.keys():
self.inputs[inp] = False
if c.split('.')[1] not in self.statuses.keys():
enabled=[]
disanled=[]
if config.get(config_section, "status."+c.split(".")[1]+".disable", fallback=False):
disabled=config.get(config_section, "status."+c.split(".")[1]+".disable", fallback=False).split(',')
if config.get(config_section, "status."+c.split(".")[1]+".enable", fallback=False):
enabled=config.get(config_section, "status."+c.split(".")[1]+".enable", fallback="").split(',')
self.statuses[c.split('.')[1]] = {'disable': disabled,
'enable': enabled,
}
setattr(self, c, config.get(config_section, c))
#print(getattr(self, 'source.closed'))
#print(dir(self))
def disable(self, sources):
pass
def enable(self, sources):
pass
def setStatus(self, status):
if hasattr(self, 'status.'+status+'.disable'):
self.disable(getattr(self, 'status.'+status+'.disable').split(','))
if hasattr(self, 'status.'+status+'.enable'):
self.enable(getattr(self, 'status.'+status+'.enable').split(','))
def updateStatus(self):
if not self.obss.online:
self.status = False
logging.info('FOUND False')
return False
for inp in self.inputs.keys():
self.inputs[inp] = self.obss.getInputStatus(self.scene, inp)
found=False
for status in self.statuses.keys():
if not found:
found = status
for enabled in self.statuses[status]['enable']:
if not self.inputs[enabled]:
found = False
if found:
for disabled in self.statuses[status]['disable']:
if self.inputs[disabled]:
found = False
if found:
self.status = found
logging.info("FOUND "+found)
return found
logging.info('FOUND False')
self.status = found
return found
def getStatus(self):
if not self.status:
self.status = self.updateStatus()
return self.status
class OBSControl: class OBSControl:
...@@ -24,8 +109,11 @@ class OBSControl: ...@@ -24,8 +109,11 @@ class OBSControl:
self.last_try = time.time()-11 self.last_try = time.time()-11
self.lastping = time.time()-20 self.lastping = time.time()-20
self.server = obs_server self.server = obs_server
self.queue = queue.Queue()
self.config_section = config_section self.config_section = config_section
self.config_options = config.options(config_section) self.config_options = config.options(config_section)
self.cl = False
self.cr = False
for c in self.config_options: for c in self.config_options:
setattr(self, c, config.get(config_section, c)) setattr(self, c, config.get(config_section, c))
...@@ -38,18 +126,26 @@ class OBSControl: ...@@ -38,18 +126,26 @@ class OBSControl:
self.cr = obs.ReqClient(host=self.host, port=self.port, password=self.password) self.cr = obs.ReqClient(host=self.host, port=self.port, password=self.password)
self.cl.callback.register(self.on_scene_item_enable_state_changed) self.cl.callback.register(self.on_scene_item_enable_state_changed)
self.setonline() self.setonline()
except Exception: except:
pass pass
def getInputStatus(self, scene, inp):
if self.cr:
return self.cr.get_scene_item_enabled(scene, int(inp)).scene_item_enabled
return False
def setonline(self): def setonline(self):
logging.info('OBS '+self.server+' ONLINE')
self.online = True self.online = True
self.queue.put({ 'event': 'SETONLINE', 'data': {'server': self.server}})
def setoffline(self): def setoffline(self):
self.online = False self.online = False
self.queue.put({'event': 'SETOFFLINE', 'data': {'server': self.server}})
def run_tasks(self): def run_tasks(self):
if not self.online and time.time()-self.last_try > 10: if not self.online and time.time()-self.last_try > 10:
print("OBS ", self.server, "starting... ") logging.info("OBS "+self.server+" starting... ")
self.start() self.start()
if self.online: if self.online:
...@@ -57,22 +153,36 @@ class OBSControl: ...@@ -57,22 +153,36 @@ class OBSControl:
try: try:
self.cr.broadcast_custom_event({'eventData': {'eventType': 'Ping', 'time': time.time()}}) self.cr.broadcast_custom_event({'eventData': {'eventType': 'Ping', 'time': time.time()}})
self.lastping = time.time() self.lastping = time.time()
logging.info("SENT Ping to "+self.server)
except: except:
self.setoffline() self.setoffline()
def on_scene_item_enable_state_changed(self, data): def on_scene_item_enable_state_changed(self, data):
print("scscene_item_enable_state_changed") logging.info("scscene_item_enable_state_changed")
print(data.attrs()) logging.info(data.attrs())
print(data.scene_item_enabled, data.scene_item_id, data.scene_name, data.scene_uuid) logging.info([data.scene_item_enabled, data.scene_item_id, data.scene_name, data.scene_uuid])
self.queue.put({'event': 'INPUTCHANGE', 'data': {'server': self.server, 'status': data.scene_item_enabled, 'scene': data.scene_name, 'source': data.scene_item_id}})
def run_obs_controller(): def run_obs_controller():
obs_servers = {} obs_servers = {}
for k in [x for x in config.sections() if 'OBS:' in x]: for k in [x for x in config.sections() if 'OBS:' in x]:
if not config.get(k, 'active', fallback=False): #if not config.get(k, 'active', fallback=False):
obs_servers[k.split(":",1)[1]] = OBSControl(k.split(":",1)[1], k) obs_servers[k.split(":",1)[1]] = OBSControl(k.split(":",1)[1], k)
obs_outputs = {}
for k in [x for x in config.sections() if 'OUTPUT:' in x]:
obss = False
if config.get(k, 'obs') in obs_servers.keys():
obss = obs_servers[config.get(k, 'obs')]
obs_outputs[k.split(":",1)[1]] = OBSOutput(k.split(":",1)[1], k, obss )
logging.info('OUTPUT '+config.get(k, 'obs')+" -> "+k.split(":",1)[1])
logging.info(obs_outputs[k.split(":",1)[1]].getStatus())
#cl = obs.EventClient(host='192.168.42.115', port=4455, password='motorol4') #cl = obs.EventClient(host='192.168.42.115', port=4455, password='motorol4')
#cr = obs.ReqClient(host='192.168.42.115', port=4455, password='motorol4') #cr = obs.ReqClient(host='192.168.42.115', port=4455, password='motorol4')
...@@ -85,10 +195,33 @@ def run_obs_controller(): ...@@ -85,10 +195,33 @@ def run_obs_controller():
now=time.time()-30 now=time.time()-30
while True: while True:
if not qobs.empty():
task=qobs.get(block=True)
if task:
logging.info('TASK INCOMING FOR OBS')
logging.info(task)
if (time.time() - now) > 30: if (time.time() - now) > 30:
now = time.time() now = time.time()
#cr.broadcast_custom_event({'eventData': {'eventType': 'Ping', 'time': time.time()}}) #cr.broadcast_custom_event({'eventData': {'eventType': 'Ping', 'time': time.time()}})
for o in obs_servers.keys(): for o in obs_servers.keys():
obs_servers[o].run_tasks() obs_servers[o].run_tasks()
if not obs_servers[o].queue.empty():
task=obs_servers[o].queue.get(block=True)
if task:
logging.info('EVENT COMING FROM OBS '+o)
logging.info(task)
event = task['event']
data = task['data']
if event == 'SETOFFLINE' or event == 'SETONLINE':
for output in obs_outputs.values():
if output.obss.server == data['server']:
output.updateStatus()
if event == 'INPUTCHANGE':
#'data': {'server': 'leeloo', 'status': False, 'scene': 'LIVE SFW', 'source': 5}}
for output in obs_outputs.values():
if output.obss.server == data['server'] and output.scene==data['scene'] and str(data['source']) in output.inputs.keys():
output.updateStatus()
time.sleep(.01) time.sleep(.01)
...@@ -22,6 +22,10 @@ import os ...@@ -22,6 +22,10 @@ import os
from utils import run_command, run_action from utils import run_command, run_action
from guiutils import get_buttons from guiutils import get_buttons
import queue import queue
import logging
logging.getLogger(__name__)
class VideoPlayer: class VideoPlayer:
def __init__(self, master, video_url): def __init__(self, master, video_url):
...@@ -81,7 +85,6 @@ def create_panel_gui(): ...@@ -81,7 +85,6 @@ def create_panel_gui():
# Buttons configuration # Buttons configuration
buttons, numrows = get_buttons() buttons, numrows = get_buttons()
print(numrows , buttons)
bh = int(55/numrows) bh = int(55/numrows)
...@@ -98,7 +101,7 @@ def create_panel_gui(): ...@@ -98,7 +101,7 @@ def create_panel_gui():
# add the buttons # add the buttons
col=0 col=0
print(buttons[row]) logging.info(buttons[row])
for b in buttons[row].keys(): for b in buttons[row].keys():
command=None command=None
if config.has_section('ACTION:'+buttons[row][b]['action']): if config.has_section('ACTION:'+buttons[row][b]['action']):
......
...@@ -15,11 +15,42 @@ ...@@ -15,11 +15,42 @@
import time import time
import sys import sys
from utils import create_daemon from utils import create_daemon
import queue
import logging
logging.getLogger(__name__)
STATUSES=[
'manual',
'open',
'close'
]
class TaskEngine():
status='init'
def process_task(self, task):
cmd, val = task.split(':', 1)
if cmd=='SETSTATUS' and val in STATUSES:
logging.info('SETSTATUS TO '+val)
if self.status != val:
self.status=val
def camstudio(): def camstudio():
engine=TaskEngine()
while True: while True:
task = qcore.get(block=True)
if task:
logging.info(task)
engine.process_task(task)
qcore.task_done()
time.sleep(.001) time.sleep(.001)
def run_camstudio(daemon=False): def run_camstudio(daemon=False):
if daemon and sys.platform != 'win32': if daemon and sys.platform != 'win32':
create_daemon() create_daemon()
......
...@@ -33,8 +33,7 @@ def run_command(command): ...@@ -33,8 +33,7 @@ def run_command(command):
def run_action(command, setstatus=None): def run_action(command, setstatus=None):
if setstatus: if setstatus:
print('SETSTATUS:'+str(setstatus)) qcore.put('SETSTATUS:'+str(setstatus), block=False)
qcore.put('SETSTATUS:'+str(setstatus))
if command: if command:
return run_command(command) return run_command(command)
......
...@@ -14,13 +14,54 @@ ...@@ -14,13 +14,54 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
from flask import Flask, render_template, request from flask import Flask, render_template, request
from utils import check_port_available, run_command, create_daemon from utils import check_port_available, run_command, create_daemon, run_action
import sys import sys
import os import os
from guiutils import get_buttons from guiutils import get_buttons
import flask_restful as restful
# Flask App Setup # Flask App Setup
flask_app = Flask('SHMCamStudio', template_folder=TEMPLATE_DIR) flask_app = Flask('SHMCamStudio', template_folder=TEMPLATE_DIR)
flask_api = restful.Api(flask_app)
class PollAPI(restful.Resource):
def _is_updated(self, request_time):
"""
Returns if resource is updated or it's the first
time it has been requested.
args:
request_time: last request timestamp
"""
return os.stat('data.txt').st_mtime > request_time
def get(self):
"""
Returns 'data.txt' content when the resource has
changed after the request time
"""
request_time = time.time()
while not self._is_updated(request_time):
time.sleep(0.5)
content = ''
with open('data.txt') as data:
content = data.read()
return {'content': content,
'date': datetime.now().strftime('%Y/%m/%d %H:%M:%S')}
class AppData(restful.Resource):
def get(self):
"""
Returns the current data content
"""
content = ''
with open('data.txt') as data:
content = data.read()
return {'content': content}
@flask_app.route('/') @flask_app.route('/')
def index(): def index():
...@@ -38,10 +79,13 @@ def index(): ...@@ -38,10 +79,13 @@ def index():
for b in buttons[row].keys(): for b in buttons[row].keys():
command=buttons[row][b]['action'] command=buttons[row][b]['action']
color=buttons[row][b]['color'] color=buttons[row][b]['color']
pollclass=''
htmlbuttons=htmlbuttons+"""<button style="color:white;background-color:"""+color+""";" class="button private button_row"""+str(row)+"""" onclick="executeCommand('"""+command+"""')"> if 'output' in buttons[row][b].keys():
pollclass='output_'+buttons[row][b]['output']
htmlbuttons=htmlbuttons+"""<button style="color:white;background-color:"""+color+""";"
class="button private button_row"""+str(row)+" "+pollclass+""" " onclick="executeCommand('"""+command+"""')">
"""+buttons[row][b]['title']+""" """+buttons[row][b]['title']+"""
</button>""" </button>"""
htmlbuttons=htmlbuttons+"</div>" htmlbuttons=htmlbuttons+"</div>"
row=row+1 row=row+1
...@@ -55,11 +99,12 @@ def execute(): ...@@ -55,11 +99,12 @@ def execute():
if config.has_section('ACTION:'+command_key): if config.has_section('ACTION:'+command_key):
command = config.get('ACTION:'+command_key, 'execute', fallback=None) command = config.get('ACTION:'+command_key, 'execute', fallback=None)
if command: setstatus=config.get('ACTION:'+command_key, 'setstatus', fallback=None)
result = run_command(command) if command or setstatus:
result = run_action(command, setstatus)
else: else:
return "No command available" return "No command available"
return result return result or 'OK'
else: else:
return "Invalid command", 400 return "Invalid command", 400
...@@ -80,6 +125,8 @@ def run_flask_app(port=5000, daemon_mode=False): ...@@ -80,6 +125,8 @@ def run_flask_app(port=5000, daemon_mode=False):
if daemon_mode and sys.platform != 'win32': if daemon_mode and sys.platform != 'win32':
create_daemon() create_daemon()
flask_api.add_resource(PollAPI, '/update')
flask_api.add_resource(AppData, '/data')
flask_app.run(host='0.0.0.0', port=port, debug=False, use_reloader=False) flask_app.run(host='0.0.0.0', port=port, debug=False, use_reloader=False)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment