248 lines
11 KiB
Python
248 lines
11 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 sys
|
|
import time
|
|
import logging
|
|
import requests
|
|
import memcache
|
|
from lib import config
|
|
from lib import obs as obsremote
|
|
|
|
from pprint import pprint
|
|
|
|
conf = config.get_config()
|
|
|
|
# Must be AFTER import config, as config sets default logging options
|
|
logging.basicConfig(format='%(asctime)s %(module)s %(levelname)s: %(message)s')
|
|
logging.getLogger().setLevel(conf['LOG_LEVEL'])
|
|
|
|
mc = memcache.Client(['localhost:11211'], debug=0)
|
|
|
|
def pushover_send(user, token, msg):
|
|
if user and token:
|
|
logging.debug('Send message to pushover')
|
|
r = requests.post("https://api.pushover.net/1/messages.json", data={ "token": token, "user": user, "message": str(msg) })
|
|
logging.debug(r)
|
|
return r
|
|
else:
|
|
return False
|
|
|
|
def countdown(t):
|
|
while t:
|
|
if logging.getLogger().level <= 20:
|
|
mins, secs = divmod(t, 60)
|
|
timeformat = '{:02d}:{:02d}'.format(mins, secs)
|
|
sys.stdout.write(timeformat + "\r")
|
|
time.sleep(1)
|
|
t -= 1
|
|
|
|
def print_info():
|
|
logging.info('-------------------------------------------------------------------------------')
|
|
logging.info('Stream Status')
|
|
logging.info("OBS Connection : %s", str(mc.get('OBS_CONNECTED')))
|
|
logging.info("OBS Streaming : %s", str(mc.get('OBS_STREAMING_STATUS')))
|
|
logging.info("OBS Scene : %s", str(mc.get('OBS_CURRENT_SCENE')))
|
|
logging.info("Bitrate : %s", str(mc.get('RTMP_BITRATE')))
|
|
logging.info("Low threshold : %s", str(conf['LOW_BITRATE']))
|
|
logging.info("Low bitrate : %s", str(mc.get('LOW_BITRATE')))
|
|
logging.info("Publishing : %s", str(mc.get('RTMP_PUBLISHING')))
|
|
logging.info('-------------------------------------------------------------------------------')
|
|
logging.info('OBS Stats')
|
|
logging.info("CPU usage : %s", str(mc.get('OBS_CPU-USAGE')))
|
|
logging.info("Memory usage : %s", str(mc.get('OBS_MEMORY-USAGE')))
|
|
logging.info("Free disk space : %s", str(mc.get('OBS_FREE-DISK-SPACE')))
|
|
logging.info("Stream timecode : %s", str(mc.get('OBS_STREAM-TIMECODE')))
|
|
logging.info("FPS : %s", str(mc.get('OBS_FPS')))
|
|
logging.info("kbits/sec. : %s", str(mc.get('OBS_KBITS-PER-SEC')))
|
|
logging.info("bytes/sec. : %s", str(mc.get('OBS_BYTES-PER-SEC')))
|
|
logging.info("Dropped frames : %s", str(mc.get('OBS_NUM-DROPPED-FRAMES')))
|
|
logging.info("Total frames : %s", str(mc.get('OBS_NUM-TOTAL-FRAMES')))
|
|
logging.info("Output skipped frames: %s", str(mc.get('OBS_OUTPUT-SKIPPED-FRAMES')))
|
|
logging.info("Output total frames : %s", str(mc.get('OBS_OUTPUT-TOTAL-FRAMES')))
|
|
logging.info("Avg. frame time : %s", str(mc.get('OBS_AVERAGE-FRAME-TIME')))
|
|
logging.info("Render total frames : %s", str(mc.get('OBS_RENDER-TOTAL-FRAMES')))
|
|
logging.info("Render missed frames : %s", str(mc.get('OBS_RENDER-MISSED-FRAMES')))
|
|
logging.info("Total stream time : %s", str(mc.get('OBS_TOTAL-STREAM-TIME')))
|
|
logging.info("Preview only : %s", str(mc.get('OBS_PREVIEW-ONLY')))
|
|
logging.info("Recording : %s", str(mc.get('OBS_RECORDING')))
|
|
logging.info("Strain : %s", str(mc.get('OBS_STRAIN')))
|
|
logging.info("Replay Buffer : %s", str(mc.get('OBS_REPLAY-BUFFER-ACTIVE')))
|
|
logging.info('-------------------------------------------------------------------------------')
|
|
logging.info("Active VLC Sources : %s", str(mc.get('OBS_ACTIVE_VLC_SOURCES')))
|
|
logging.info("Active Filters : %s", str(mc.get('OBS_ACTIVE_FILTERS')))
|
|
logging.info("Muted Audio Sources : %s", str(mc.get('OBS_MUTED_AUDIO')))
|
|
logging.info("Volume Settings : %s", str(mc.get('OBS_VOLUME_SETTINGS')))
|
|
logging.info('-------------------------------------------------------------------------------')
|
|
|
|
return True
|
|
|
|
def main():
|
|
o = False
|
|
obs_connected = False
|
|
obs_streaming_info_resend = True
|
|
rtmp_error_info_resend = True
|
|
forced_scene_info_resend = True
|
|
low_bitrate_info_resend = True
|
|
switch_to_brb = False
|
|
|
|
pu = conf['PUSHOVER_USER_KEY']
|
|
pt = conf['PUSHOVER_APP_TOKEN']
|
|
|
|
obs_remote = obsremote.ObsRemote(conf)
|
|
current_scene = obs_remote.get_scene()
|
|
|
|
mc.set('OBS_CONNECTED', "False")
|
|
mc.set('OBS_STREAMING_STATUS', "False")
|
|
mc.set('OBS_CURRENT_SCENE', current_scene)
|
|
|
|
active_vlc_sources = ""
|
|
active_filters = ""
|
|
muted_audio = ""
|
|
|
|
while True:
|
|
while not obs_connected:
|
|
logging.debug('Trying to connect to OBS')
|
|
obs = obs_remote.connect()
|
|
if obs:
|
|
obs_connected = True
|
|
logging.debug('OBS connection established')
|
|
pushover_send(pu,pt,'OBS connection established')
|
|
mc.set('OBS_CONNECTED', "True")
|
|
else:
|
|
logging.debug('OBS is not connected, waiting...')
|
|
pushover_send(pu,pt,'OBS is not connected, next connection attempt will be in 60 seconds.')
|
|
print_info()
|
|
mc.set('OBS_CONNECTED', "False")
|
|
obs_connected = False
|
|
countdown(60)
|
|
continue
|
|
|
|
mc.set('OBS_CONNECTED', "True")
|
|
|
|
if not obs_remote.is_streaming():
|
|
logging.info('OBS is not streaming')
|
|
mc.set('OBS_STREAMING_STATUS', "False")
|
|
current_scene = obs_remote.get_scene()
|
|
mc.set('OBS_CURRENT_SCENE', current_scene)
|
|
if obs_streaming_info_resend:
|
|
pushover_send(pu,pt,'OBS is not streaming, will check again in 5 seconds. This message is only sent once.')
|
|
obs_streaming_info_resend = False
|
|
print_info()
|
|
countdown(5)
|
|
else:
|
|
obs_streaming_info_resend = True
|
|
|
|
if mc.get("rtmp_error"):
|
|
logging.error('There was an RTMP error')
|
|
if rtmp_error_info_resend:
|
|
pushover_send(pu,pt,'There was an RTMP error, will check again in 30 seconds. This message is only sent once.')
|
|
rtmp_error_info_resend = False
|
|
print_info()
|
|
countdown(30)
|
|
continue
|
|
else:
|
|
rtmp_error_info_resend = True
|
|
|
|
obs_error = obs_remote.error()
|
|
if obs_error:
|
|
logging.error('There was an OBS error (%s)', obs_error)
|
|
pushover_send(pu,pt,'There was an OBS error. Will try again in 10 seconds. Stop the stream to stop this message from being sent.')
|
|
print_info()
|
|
countdown(10)
|
|
continue
|
|
else:
|
|
mc.set('OBS_CONNECTED', "True")
|
|
|
|
o = obs_remote.get()
|
|
|
|
try:
|
|
logging.info('Fetching OBS details')
|
|
for key, value in o.items():
|
|
mc.set('OBS_' + str(key).upper(), value)
|
|
except:
|
|
logging.warning('Error submitting OBS data to memcache')
|
|
|
|
current_scene = obs_remote.get_scene()
|
|
mc.set('OBS_CURRENT_SCENE', current_scene)
|
|
current_streaming_status = obs_remote.is_streaming()
|
|
mc.set('OBS_STREAMING_STATUS', current_streaming_status)
|
|
|
|
sources_and_filter = obs_remote.get_sources_and_filter()
|
|
|
|
if sources_and_filter:
|
|
mc.set('OBS_ACTIVE_VLC_SOURCES', sources_and_filter['active_vlc_sources'])
|
|
mc.set('OBS_ACTIVE_FILTERS', sources_and_filter['active_filters'])
|
|
mc.set('OBS_ACTIVE_AUDIO', sources_and_filter['active_audio'])
|
|
mc.set('OBS_MUTED_VLC_SOURCES', sources_and_filter['muted_vlc_sources'])
|
|
mc.set('OBS_MUTED_FILTERS', sources_and_filter['muted_filters'])
|
|
mc.set('OBS_MUTED_AUDIO', sources_and_filter['muted_audio'])
|
|
mc.set('OBS_VOLUME_SETTINGS', sources_and_filter['volume_settings'])
|
|
|
|
if not mc.get('RTMP_PUBLISHING') or mc.get('RTMP_LOW_BITRATE'):
|
|
logging.debug('No RTMP client is publishing with an sufficient bitrate')
|
|
if low_bitrate_info_resend:
|
|
pushover_send(pu,pt,'No RTMP client is publishing with an sufficient bitrate. Bitrate is '+str(mc.get('RTMP_BITRATE'))+' kbit/sec. - This message is only sent once.')
|
|
low_bitrate_info_resend = False
|
|
switch_to_brb = True
|
|
else:
|
|
switch_to_brb = False
|
|
low_bitrate_info_resend = True
|
|
|
|
# Check if we're in a scene we are not allowed to switch automatically.
|
|
if current_scene.startswith('FORCED') or current_scene == conf['OBS_OFFLINE_SCENE']:
|
|
logging.info('FORCED/OFFLINE scene active, will not switch scene.')
|
|
if forced_scene_info_resend:
|
|
pushover_send(pu,pt,'FORCED/OFFLINE scene active, will not switch scene. This message is only sent once.')
|
|
forced_scene_info_resend = False
|
|
print_info()
|
|
time.sleep(1)
|
|
continue
|
|
|
|
# Check if we need to automatically switch scenes.
|
|
if switch_to_brb:
|
|
if current_scene != conf['OBS_BRB_SCENE']:
|
|
logging.debug('Switching to BRB scene')
|
|
pushover_send(pu,pt,'Switching to BRB scene. The scene will be active for at least 5 seconds.')
|
|
obs_remote.set_scene(conf['OBS_BRB_SCENE'])
|
|
countdown(10)
|
|
else:
|
|
if current_scene != conf['OBS_LIVE_SCENE']:
|
|
logging.debug('Switching from BRB to LIVE')
|
|
pushover_send(pu,pt,'Switching to LIVE scene. Bitrate is '+str(mc.get('RTMP_BITRATE'))+' kbit/sec.')
|
|
obs_remote.set_scene(conf['OBS_LIVE_SCENE'])
|
|
forced_scene_info_resend = True
|
|
|
|
if o:
|
|
if o['streaming'] == "Exit":
|
|
logging.critical('OBS was shut down!')
|
|
pushover_send(pu,pt,'OBS was shut down! Will check again in 30 seconds. This message is only sent once.')
|
|
obs_connected = False
|
|
obs_streaming_info_resend = False
|
|
rtmp_error_info_resend = False
|
|
time.sleep(30)
|
|
|
|
print_info()
|
|
time.sleep(1)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|