Initial implementation of the restreamer

Supports serving segments, listing segments for an hour, and generating playlists so it can stream.
pull/5/head
Mike Lang 6 years ago
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…
Cancel
Save