#!/usr/bin/env python3 from flask import Flask, request, make_response, session, redirect, render_template, send_from_directory, jsonify from time import gmtime, strftime, time from uuid import uuid4 from bs4 import BeautifulSoup import requests import json import sqlite3 import base64 import configparser import os """ SQLite3 Database Schema CREATE TABLE channels ( "id" INTEGER PRIMARY KEY, "name" TEXT NOT NULL, "key" TEXT NOT NULL, "live_at" TEXT, "password" TEXT, "user_uuid" TEXT , "last_stream" TEXT); CREATE TABLE signed_users ( "id" INTEGER PRIMARY KEY, "uuid" TEXT NOT NULL, "name" TEXT NOT NULL ); """ # Load configuration config = configparser.ConfigParser() config['Streaming'] = { 'Database': 'streaming.db', 'StreamServer': 'https://tv.icynet.eu/live/', 'ServerHost': 'icynet.eu', 'PublishAddress': 'rtmp://{host}:1935/hls-live/{streamer}', 'Secret': 'changeme', } config['Auth'] = { 'Server': 'http://localhost:8282', 'Redirect': 'http://localhost:5000/auth/_callback/', } config['OAuth2'] = { 'ClientID': '1', 'ClientSecret': 'changeme', } if os.path.exists('config.ini'): config.read('config.ini') with open('config.ini', 'w') as configfile: config.write(configfile) # App settings app = Flask(__name__) app.secret_key = config['Streaming']['Secret'] app.config['SESSION_TYPE'] = 'redis' auth_image = "{server}/api/avatar/{uuid}" oauth_auth = "{server}/oauth2/authorize?response_type=code&state={state}&redirect_uri={redirect}&client_id={client}&scope=image" stream_server = config['Streaming']['StreamServer'] auth_server = config['Auth']['Server'] oauth_redirect = config['Auth']['Redirect'] oauth_id = int(config['OAuth2']['ClientID']) oauth_secret = config['OAuth2']['ClientSecret'] # Database conn = sqlite3.connect(config['Streaming']['Database'], check_same_thread=False) # Streamer memcache stream_cache = {} # Stat memcache stat_cache = False stat_cache_updated = 0 # Check if user is a streamer def valid_streamer(uuid): streamer = None if 'uuid' in session: if session['uuid'] in stream_cache: streamer = stream_cache[session['uuid']] else: # Find key in database data = conn.execute('SELECT * FROM channels WHERE user_uuid=?', (session['uuid'],)) row = data.fetchone() # Deny stream publish if row: streamer = row[2] return streamer def pull_stream_metrics(uuid): global stat_cache global stat_cache_updated updated = False if not stat_cache or stat_cache_updated < int(time()) - 5: metric_path = stream_server + "stat" r = requests.get(metric_path) stat_cache = BeautifulSoup(r.text, "xml") updated = True print("Updating per request..") allapps = stat_cache.find_all("application") relevant = None for app in allapps: if str(app.find("name").string) == "live": relevant = app if not relevant: return None streams = relevant.find_all("stream") if not streams: return None if updated: stat_cache_updated = int(time()) relevant = None for stream in streams: name = stream.find("name").string if name == uuid: relevant = stream if not relevant: return None data = { 'time' : relevant.time.string, 'bytes' : relevant.bytes_in.string, 'video': { 'width': relevant.meta.video.width.string, 'height': relevant.meta.video.height.string, 'frame_rate': relevant.meta.video.frame_rate.string, 'codec': relevant.meta.video.codec.string, }, 'audio': { 'sample_rate': relevant.meta.audio.sample_rate.string, 'channels': relevant.meta.audio.channels.string, 'codec': relevant.meta.audio.codec.string, } } return data @app.route("/") def index(): streamer = False if 'uuid' in session: if valid_streamer(session['uuid']): streamer = True return render_template("index.html", streamer = streamer) @app.route("/dashboard") def dashboard(): if not 'uuid' in session: return redirect("/login", code = 302) streamkey = valid_streamer(session['uuid']) if not streamkey: return make_response("Unauthorized.", 402) return render_template("dashboard.html", stream = streamkey, server = "rtmp://" + config['Streaming']['ServerHost'] + "/live/") @app.route("/dashboard/stats") def dashboard_stats(): if not 'uuid' in session: return jsonify({'error': 'Unauthorized'}) streamkey = valid_streamer(session['uuid']) if not streamkey: return jsonify({'error': 'Unauthorized'}) data = pull_stream_metrics(streamkey) if not data: return jsonify({'error': 'No data was returned..'}) return jsonify(data) @app.route("/dashboard/data") def dashboard_data(): if not 'uuid' in session: return jsonify({'error': 'Unauthorized'}) streamkey = valid_streamer(session['uuid']) if not streamkey: return jsonify({'error': 'Unauthorized'}) # Find key in database data = conn.execute('SELECT * FROM channels WHERE key=?', (streamkey,)) row = data.fetchone() if not row: return jsonify({'error': 'Unauthorized'}) livedate = row[3] data = { 'name': row[1], 'key': streamkey, 'uuid': session['uuid'], 'live': livedate != None, 'live_at': livedate, 'last_stream': row[6], } return jsonify(data) # Called when starting publishing @app.route("/publish", methods=["POST"]) def publish(): print(json.dumps(request.form, ensure_ascii=False)) streamkey = request.form["name"] # Find key in database data = conn.execute('SELECT * FROM channels WHERE key=?', (streamkey,)) row = data.fetchone() # Deny stream publish if not row: return make_response("Request Denied", 400) streamer = row[1] print("Streamer %s has started streaming!" % streamer) # Redirect stream publish to stream name url = config['Streaming']['PublishAddress'].format(streamer = streamer, host = "127.0.0.1") response = make_response(url, 302) response.headers["Location"] = url # Update database with stream timestamp starttime = strftime("%Y-%m-%d %H:%M:%S", gmtime()) conn.execute('UPDATE channels SET live_at=? WHERE id=?', (starttime, row[0])) conn.commit() return response # Called when stopped publishing @app.route("/publish_done", methods=["POST"]) def publish_done(): streamkey = request.form["name"] # Update database with stream end time endtime = strftime("%Y-%m-%d %H:%M:%S", gmtime()) conn.execute('UPDATE channels SET live_at=NULL, last_stream=? WHERE key=?', (endtime, streamkey,)) conn.commit() return "OK" @app.route("/login") def login(): if 'uuid' in session: return make_response("Already authenticated as %s!" % session['username']) state = str(uuid4()) session['state'] = state return redirect(oauth_auth.format(state = state, client = oauth_id, redirect = oauth_redirect, server = auth_server), code = 302) @app.route("/logout") def logout(): session.clear() return redirect("/", code = 302) @app.route("/auth/_callback/") def cb(): if not 'state' in session: return make_response("Something went wrong!", 402) code = request.args.get('code') state = request.args.get('state') if session['state'] != state: return make_response("Something went wrong!", 402) if not code: return make_response("Authorization denied by user", 402) # Get access token r = requests.post(auth_server + "/oauth2/token", data = { 'grant_type': 'authorization_code', 'code': code, 'redirect_uri': oauth_redirect, 'client_id': oauth_id, 'client_secret': oauth_secret }, headers = { 'Authorization': 'Basic ' + str(base64.b64encode(bytes("%s:%s" % (oauth_id, oauth_secret), 'utf-8'))) }) res_token = None try: res_token = r.json() except ValueError: return make_response("Something went wrong while getting an access token!") if 'error' in res_token: return make_response("%s: %s" % (res_token['error'], res_token['error_description']), 500) token = res_token['access_token'] # Get user info ru = requests.get(auth_server + "/oauth2/user", headers = { 'Authorization': 'Bearer ' + token }) res_uinfo = None try: res_uinfo = ru.json() except ValueError: return make_response("Something went wrong while getting user information!") udata = conn.execute('SELECT * FROM signed_users WHERE uuid=?', (res_uinfo['uuid'],)) row = udata.fetchone() if not row: conn.execute('INSERT INTO signed_users (uuid, name) VALUES (?, ?)', (res_uinfo['uuid'], res_uinfo['username'])) conn.commit() session['uuid'] = res_uinfo['uuid'] session['username'] = res_uinfo['username'] return redirect("/", code = 302) @app.route("/watch/") def watch(name): return render_template("player.html", name = name, server = stream_server) @app.route("/player/") def watch_old(name): return redirect("/watch/%s" % name, code = 302) @app.route("/dist/") def dist(path): return send_from_directory("dist", path) if __name__ == '__main__': app.secret_key = '00-wegrhr[gqw[er=1ew qwergfdq.///**+' app.debug = True app.run()