212 lines
9.2 KiB
Python
212 lines
9.2 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
twitch-irl-docker
|
|
Copyright (C) 2022 gpkvt
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero General Public License as published
|
|
by the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
"""
|
|
|
|
import obs_settings
|
|
import cgi, cgitb
|
|
from obswebsocket import obsws, requests
|
|
from pprint import pprint
|
|
|
|
if obs_settings.debug:
|
|
cgitb.enable()
|
|
|
|
print("HTTP/1.0 200 OK")
|
|
print("Content-type:text/html;charset=utf-8\r\n")
|
|
|
|
form = cgi.FieldStorage()
|
|
action = form.getvalue('action')
|
|
param = form.getvalue('param')
|
|
|
|
host = obs_settings.host
|
|
port = obs_settings.port
|
|
password = obs_settings.password
|
|
dayflat = obs_settings.dayflat
|
|
extdata = obs_settings.extdata
|
|
liveu = obs_settings.liveu
|
|
offline_scene = obs_settings.offline_scene
|
|
channel = obs_settings.channel
|
|
chat_height = obs_settings.chat_height
|
|
tts = obs_settings.tts
|
|
tts_url = obs_settings.tts_url
|
|
|
|
if not chat_height:
|
|
chat_height = int(500)
|
|
|
|
connected = False
|
|
ws = obsws(host, port, password)
|
|
|
|
try:
|
|
ws.connect()
|
|
connected = True
|
|
except:
|
|
connected = False
|
|
|
|
if connected:
|
|
scenes = ws.call(requests.GetSceneList())
|
|
sources = ws.call(requests.GetSourcesList())
|
|
|
|
print('''<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<title>OBS Remote</title>
|
|
<style>
|
|
body { font-family:sans-serif;margin:0px;padding:0px;font-size:smaller; }
|
|
button { width:100%;cursor:pointer;margin:0px;padding-top:10px;padding-bottom:10px;background-color:#eee;border:1px solid #888;border-radius:5px;text-align:center;line-height:150px;font-weight:bold;font-size:xx-large; }
|
|
#content { width:100%;margin-left:auto;margin-right:auto; }
|
|
div.status { text-align:center;background-color:#009bea;color:#fff;border:1px solid #888;border-radius:5px;width:99%;margin:5px;margin-left:auto;margin-right:auto;padding-top:10px;padding-bottom:10px; }
|
|
div.stream { width:99%;margin:5px;margin-left:auto;margin-right:auto;margin-top:25px;margin-bottom:25px; }
|
|
div.audio { width:99%;margin:5px;margin-left:auto;margin-right:auto; }
|
|
div.scene { width:99%;margin:5px;margin-left:auto;margin-right:auto; }
|
|
div.source { width:99%;margin:5px;margin-left:auto;margin-right:auto; }
|
|
div.filter { width:99%;margin:5px;margin-left:auto;margin-right:auto; }
|
|
button.scene.active { background-color:#333;color:#eee; }
|
|
button.stream { background-color:green;color:#fff; }
|
|
button.stream.active { background-color:red;color:#fff; }
|
|
button.audio { background-color:green;color:#fff; }
|
|
button.audio.active { background-color:red;color:#fff; }
|
|
button.source { background-color:#009bea;color:#fff; }
|
|
button.filter.active { background-color:red;color:#fff; }
|
|
button.filter { background-color:green;color:#fff; }
|
|
.modal { display:none;position:fixed;z-index:1000;top:0;left:0;height:100%;width:100%;background:rgba(255,255,255,.8) url('./ajax-loader.gif') 50% 50% no-repeat;}
|
|
body.loading .modal { overflow:hidden; }
|
|
body.loading .modal { display:block; }
|
|
div.error { display:none;position:absolute;z-index:1000;top:50%;left:50%;margin-top:-180px;width:600px;margin-left:-350px;padding:50px;background:rgba(255,255,255,.9);border:10px solid #CC0000;color:#CC0000;font-weight:bold; }
|
|
#ajaxerror.visible { display:block; }
|
|
#obserror.visible { display:block; }
|
|
#lowbitrate.visible { display:block; }
|
|
</style>
|
|
<script src="./jquery.js"></script>''')
|
|
|
|
if connected:
|
|
print('<script src="./remote.js"></script>')
|
|
|
|
print('</head>')
|
|
print('<body><div id="content">')
|
|
|
|
if connected:
|
|
print('<div id="status">')
|
|
print(' <div class="status" id="info">N/A</div>')
|
|
if liveu:
|
|
print(' <div class="status" id="liveu">N/A</div>')
|
|
if extdata:
|
|
print(' <div class="status" id="extdata">N/A</div')
|
|
print('</div>')
|
|
|
|
if channel:
|
|
print('<div id="chat">')
|
|
print(' <iframe id="twitchchat" style="display:none;" scrolling="0" frameborder="0" src="https://www.twitch.tv/embed/{0}/chat?parent=localhost" height="500px" width="100%"></iframe>'.format(str(channel)))
|
|
print(' <div class="status" onClick="$(\'#twitchchat\').fadeToggle(\'slow\', \'linear\');">Show/Hide chat</div>')
|
|
print('</div>')
|
|
|
|
if tts:
|
|
print('<div id="tts">')
|
|
print(' <iframe id="ttsframe" style="display:none;" scrolling="0" frameborder="0" src="{0]" height="500px" width="100%"></iframe>'.format(str(tts_url)))
|
|
print(' <div class="status" onClick="$(\'#ttsframe\').fadeToggle(\'slow\', \'linear\');">Show/Hide chat</div>')
|
|
print('</div>')
|
|
|
|
print('<div id="stream">')
|
|
print(' <div id="streamform">')
|
|
if dayflat:
|
|
print(' <div class="stream"><button id="startbutton" class="stream" onclick="clickButton({{cmd="stream", action="start", dayflat=True}});">Start Stream</button></div>')
|
|
else:
|
|
print(' <div class="stream"><button id="startbutton" class="stream" onclick="clickButton({{cmd="stream", action="start"}});">Start Stream</button></div>')
|
|
print(' </div>')
|
|
print('</div>')
|
|
|
|
print('<div id="scene">')
|
|
print(' <div id="sceneform">')
|
|
for s in scenes.getScenes():
|
|
id = str(s['name'])
|
|
print(' <div class="scene"><button id="{0}" class="scene" onclick=\'clickButton({{cmd: "scene", action: "{0}"}});\'>{0}</button></div>'.format(id))
|
|
print(' </div>')
|
|
print('</div>')
|
|
|
|
print('<div id="source">')
|
|
print(' <div id="sourceform">')
|
|
for s in sources.getSources():
|
|
if s['typeId'] == "vlc_source":
|
|
id = str(s['name'])
|
|
print(' <div class="source"><button id="{0}" class="source" onclick=\'clickButton({{cmd: "source", action: "{0}"}});\'>Reload {0}</button></div>'.format(id))
|
|
print(' <div class="filter">')
|
|
filters = ws.call(requests.GetSourceFilters(s['name']))
|
|
for f in filters.getFilters():
|
|
id = str(s['name'])+'|'+str(f['name'])
|
|
filter = str(s['name'])+'|'+str(f['name'])
|
|
print(' <button id="{0}" class="filter" onclick=\'clickButton({{cmd: "source", action: "filter", filter: "{1}", state: "1"}});\'>{0}</button>'.format(id, filter))
|
|
print(' </div>')
|
|
print(' </div>')
|
|
print('</div>')
|
|
|
|
print('<div id="audio">')
|
|
print(' <div id="audioform">')
|
|
for s in sources.getSources():
|
|
id = str(s['name'])
|
|
if s['typeId'] == 'ffmpeg_source' or s['typeId'] == 'wasapi_input_capture' or s['typeId'] == 'wasapi_output_capture':
|
|
print(' <div class="audio"><button id="{0}" class="audio" onclick=\'clickButton({{cmd: "audio", action: "{0}"}});\'>{0}</button></div>'.format(id))
|
|
print(' </div>')
|
|
print('</div>')
|
|
else:
|
|
print('<div id="fatal"><center><p class="error fatal">FATAL: Could not connect to OBS.</p></center></div>')
|
|
|
|
print('</div>')
|
|
print('''<div class="modal"></div>
|
|
<script>
|
|
$(document).ready(function() {
|
|
$("body").addClass("loading");
|
|
setTimeout(function(){
|
|
console.log('removing loading')
|
|
$("body").removeClass("loading");
|
|
},3000);
|
|
});
|
|
</script>
|
|
<div id="ajaxerror" class="error">There was an error fetching the current state, will retry...<br /><br />Stream might be in BRB scene but we are unable to check, right now.<br /><br />Please note that it might take some seconds until the proper state will be displayed after this error is gone.</div>
|
|
<div id="obserror" class="error">Connecting to OBS...</div>
|
|
<div id="lowbitrate" class="error">The bitrate is low, stream is on hold...</div>
|
|
''')
|
|
if obs_settings.extdata:
|
|
print('''
|
|
<script>
|
|
$(document).ready(function() {
|
|
setInterval(function() {
|
|
$.ajax({
|
|
datatype: "json",
|
|
url: "/ext_data.py",
|
|
cache: false,
|
|
timeout: 2000,
|
|
success: function(result) {
|
|
json = JSON.stringify(result, null, 4);
|
|
json = json.replaceAll(',',' - ');
|
|
json = json.replaceAll('"','');
|
|
json = json.substring(1, json.length-1);
|
|
$('#extdata').html(json);
|
|
},
|
|
error: function(xhr, ajaxOptions, thrownError) {
|
|
console.log("Ext data error");
|
|
console.log(xhr.status);
|
|
console.log(thrownError);
|
|
}
|
|
});
|
|
}, 1000);
|
|
});
|
|
</script>
|
|
''')
|
|
print('</body>')
|
|
print('</html>')
|