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",
|
||||
"version": "2.0.0",
|
||||
"version": "2.1.0",
|
||||
"description": "IcyTV - nginx-rtmp-server authenticator",
|
||||
"main": "index.js",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"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": {
|
||||
"bootstrap": "^4.3.1",
|
||||
|
Reference in New Issue
Block a user