This repository has been archived on 2024-05-09. You can view files and clone it, but cannot push or open issues or pull requests.
icytv/app.py
2019-01-11 02:59:44 +02:00

355 lines
9.5 KiB
Python

#!/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/<name>")
def watch(name):
return render_template("player.html", name = name, server = stream_server)
@app.route("/player/<name>")
def watch_old(name):
return redirect("/watch/%s" % name, code = 302)
@app.route("/dist/<path:path>")
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()