add package.json scripts
This commit is contained in:
parent
a2a80bd4c4
commit
7ea83415e8
354
app.py
354
app.py
@ -1,354 +0,0 @@
|
|||||||
#!/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()
|
|
@ -1,13 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "icytv",
|
"name": "icytv",
|
||||||
"version": "2.0.0",
|
"version": "2.1.0",
|
||||||
"description": "IcyTV - nginx-rtmp-server authenticator",
|
"description": "IcyTV - nginx-rtmp-server authenticator",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"watch": "webpack -w --mode=development --log-level=debug",
|
"watch": "webpack -w --mode=development --log-level=debug",
|
||||||
"build": "webpack -p"
|
"build": "webpack -p",
|
||||||
|
"start": "node app.js",
|
||||||
|
"serve": "NODE_ENV=\"development\" node app.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"bootstrap": "^4.3.1",
|
"bootstrap": "^4.3.1",
|
||||||
|
Reference in New Issue
Block a user