twitch-irl-docker/build/www/index.py

203 lines
8.8 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
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>')
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>')