mirror of https://github.com/ekimekim/wubloader
Initial implementation of the restreamer
Supports serving segments, listing segments for an hour, and generating playlists so it can stream.pull/5/head
parent
ee8f8f6571
commit
bab2d15d6e
@ -0,0 +1,14 @@
|
||||
|
||||
import gevent.monkey
|
||||
gevent.monkey.patch_all()
|
||||
|
||||
import logging
|
||||
|
||||
import argh
|
||||
|
||||
from restreamer.main import main
|
||||
|
||||
LOG_FORMAT = "[%(asctime)s] %(levelname)8s %(name)s(%(module)s:%(lineno)d): %(message)s"
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format=LOG_FORMAT)
|
||||
argh.dispatch_command(main)
|
@ -0,0 +1,16 @@
|
||||
|
||||
|
||||
def generate_master(playlists):
|
||||
"""Generate master playlist. Playlists arg should be a map {name: url}.
|
||||
Little validation or encoding is done - please try to keep the names valid
|
||||
without escaping.
|
||||
"""
|
||||
lines = ["#EXTM3U"]
|
||||
for name, url in playlists.items():
|
||||
lines += [
|
||||
# We name each variant with a VIDEO rendition with no url
|
||||
'#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="{name}",NAME="{name}",AUTOSELECT=YES,DEFAULT=YES'.format(name=name),
|
||||
'#EXT-X-STREAM-INF:VIDEO="{name}"'.format(name=name),
|
||||
url,
|
||||
]
|
||||
return "\n".join(lines) + '\n'
|
@ -0,0 +1,84 @@
|
||||
|
||||
import errno
|
||||
import json
|
||||
import os
|
||||
|
||||
from flask import Flask, url_for, request, abort
|
||||
from gevent.pywsgi import WSGIServer
|
||||
|
||||
import generate_hls
|
||||
|
||||
|
||||
app = Flask('restreamer', static_url_path='/segments')
|
||||
|
||||
|
||||
"""
|
||||
The restreamer is a simple http api for listing available segments and generating
|
||||
HLS playlists for them.
|
||||
|
||||
The segments themselves are ideally to be served by some external webserver
|
||||
under "/segments/<stream>/<variant>/<hour>/<filename>" (ie. with BASE_DIR under "/segments"),
|
||||
though this server will also serve them if requested.
|
||||
"""
|
||||
|
||||
|
||||
def listdir(path, error=True):
|
||||
"""List files in path, excluding hidden files.
|
||||
Behaviour when path doesn't exist depends on error arg.
|
||||
If error is True, raise 404. Otherwise, return [].
|
||||
"""
|
||||
try:
|
||||
return [name for name in os.listdir(path) if not name.startswith('.')]
|
||||
except OSError as e:
|
||||
if e.errno != errno.ENOENT:
|
||||
raise
|
||||
if error:
|
||||
abort(404)
|
||||
return []
|
||||
|
||||
|
||||
@app.route('/files/<stream>/<variant>/<hour>')
|
||||
def list_segments(stream, variant, hour):
|
||||
"""Returns a JSON list of segment files for a given stream, variant and hour.
|
||||
Returns empty list on non-existant streams, etc.
|
||||
"""
|
||||
# Check no-one's being sneaky with path traversal or hidden folders
|
||||
if any(arg.startswith('.') for arg in (stream, variant, hour)):
|
||||
return "Parts may not start with period", 403
|
||||
path = os.path.join(
|
||||
app.static_folder,
|
||||
stream,
|
||||
variant,
|
||||
hour,
|
||||
)
|
||||
return json.dumps(listdir(path, error=False))
|
||||
|
||||
|
||||
@app.route('/playlist/<stream>.m3u8')
|
||||
def generate_master_playlist(stream):
|
||||
"""Returns a HLS master playlist for the given stream.
|
||||
Takes optional params:
|
||||
start, end: The time to begin and end the stream at.
|
||||
See generate_media_playlist for details.
|
||||
"""
|
||||
# path traversal / hidden folders
|
||||
if stream.startswith('.'):
|
||||
return "Stream may not start with period", 403
|
||||
variants = listdir(os.path.join(app.static_folder, stream))
|
||||
playlists = {
|
||||
variant: url_for('generate_media_playlist', stream=stream, variant=variant, **request.args)
|
||||
for variant in variants
|
||||
}
|
||||
return generate_hls.generate_master(playlists)
|
||||
|
||||
|
||||
@app.route('/playlist/<stream>/<variant>.m3u8')
|
||||
def generate_media_playlist(stream, variant):
|
||||
# TODO
|
||||
return "Not Implemented", 501
|
||||
|
||||
|
||||
def main(host='0.0.0.0', port=8000, base_dir='.'):
|
||||
app.static_folder = base_dir
|
||||
server = WSGIServer((host, port), app)
|
||||
server.serve_forever()
|
Loading…
Reference in New Issue