initial commit
commit
142d08a06c
@ -0,0 +1,14 @@
|
|||||||
|
# Beam - a simple video streaming service
|
||||||
|
|
||||||
|
Two scripts for setting up local video streaming (audio might work aswell)
|
||||||
|
|
||||||
|
## Server
|
||||||
|
Run `server.py` with python3, the only dependency is flask and vlc player. This will open a server on port 5005, listening for these two routes:
|
||||||
|
|
||||||
|
* `/open?host=<host>[&port=<port>]` This will run vlc on the server and attempt to stream from `http://<host>:<port>/stream` (default port is 4040). The response will contain the session ID that can be used to control the playback
|
||||||
|
* `/close/<id>` will close the vlc player from the specified connection id
|
||||||
|
|
||||||
|
|
||||||
|
## Client
|
||||||
|
|
||||||
|
Running `client.py <video file>` will create a symlink inside an empty temporary directory and start an http server there. Then it will call the `/open` API of the server specified in the `BEAM_SERVER` variable inside. Once evrything is running, it waits for any user input and will then clean up (stop the server, remove temp dir, call close on the server)
|
@ -0,0 +1,77 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import socket
|
||||||
|
import secrets
|
||||||
|
import tempfile
|
||||||
|
import http.server
|
||||||
|
import socketserver
|
||||||
|
|
||||||
|
from threading import Thread
|
||||||
|
from urllib.request import urlopen
|
||||||
|
|
||||||
|
PORT = 4040
|
||||||
|
BEAM_SERVER = '192.168.0.3:5005'
|
||||||
|
|
||||||
|
|
||||||
|
d = tempfile.TemporaryDirectory()
|
||||||
|
|
||||||
|
print("using tempdir " + d.name)
|
||||||
|
|
||||||
|
def handler_for_dir(dir):
|
||||||
|
def myhandler(*args, **kwargs):
|
||||||
|
kwargs['directory'] = dir
|
||||||
|
return http.server.SimpleHTTPRequestHandler(*args, **kwargs)
|
||||||
|
|
||||||
|
return myhandler
|
||||||
|
|
||||||
|
def get_request(url):
|
||||||
|
resp = urlopen(url)
|
||||||
|
return (200 <= resp.status <= 299, "\n".join([d.decode() for d in resp.readlines()]))
|
||||||
|
|
||||||
|
# get ip address (sourced from https://stackoverflow.com/a/28950776/4141651)
|
||||||
|
def get_ip():
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
try:
|
||||||
|
# doesn't even have to be reachable
|
||||||
|
s.connect(('10.255.255.255', 1))
|
||||||
|
IP = s.getsockname()[0]
|
||||||
|
except Exception:
|
||||||
|
IP = '127.0.0.1'
|
||||||
|
finally:
|
||||||
|
s.close()
|
||||||
|
return IP
|
||||||
|
|
||||||
|
target = sys.argv[1]
|
||||||
|
|
||||||
|
id = secrets.token_hex(16)
|
||||||
|
|
||||||
|
# symlink target to tempdir so we only expose a single file
|
||||||
|
os.symlink(os.path.abspath(target), d.name + '/stream')
|
||||||
|
|
||||||
|
handler = handler_for_dir(d.name + '/')
|
||||||
|
token = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
with http.server.HTTPServer(("0.0.0.0", PORT), handler) as httpd:
|
||||||
|
try:
|
||||||
|
print("serving at port", PORT)
|
||||||
|
t = Thread(target=httpd.serve_forever)
|
||||||
|
t.start()
|
||||||
|
print("calling beam target...")
|
||||||
|
host = get_ip()
|
||||||
|
resp = get_request(f'http://{BEAM_SERVER}/open?host={host}&port={PORT}')
|
||||||
|
if resp[0]:
|
||||||
|
token = resp[1].strip()
|
||||||
|
print(f"successfully started video - session {token}")
|
||||||
|
input("Just press enter when you are done...")
|
||||||
|
else:
|
||||||
|
print("Error statrtig video!")
|
||||||
|
finally:
|
||||||
|
print("shutting down server")
|
||||||
|
httpd.shutdown()
|
||||||
|
|
||||||
|
finally:
|
||||||
|
print("cleaning up")
|
||||||
|
if token:
|
||||||
|
get_request(f'http://{BEAM_SERVER}/stop/{token}')
|
||||||
|
d.cleanup()
|
@ -0,0 +1,11 @@
|
|||||||
|
with import <nixpkgs> {};
|
||||||
|
|
||||||
|
stdenv.mkDerivation rec {
|
||||||
|
name = "beam-env";
|
||||||
|
|
||||||
|
buildInputs = [
|
||||||
|
(python3.withPackages (ps: with ps; [
|
||||||
|
flask
|
||||||
|
]))
|
||||||
|
];
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
flask
|
@ -0,0 +1,48 @@
|
|||||||
|
import secrets
|
||||||
|
|
||||||
|
from time import sleep
|
||||||
|
from flask import Flask, request
|
||||||
|
from subprocess import Popen, STDOUT, PIPE
|
||||||
|
|
||||||
|
SESSIONS = dict()
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
@app.route('/open')
|
||||||
|
def open_stream():
|
||||||
|
url = "http://" + request.args['host'] + ':' + request.args.get('port', '4040') + '/stream'
|
||||||
|
id = secrets.token_hex(16)
|
||||||
|
SESSIONS[id] = {
|
||||||
|
'url': url,
|
||||||
|
'id': id,
|
||||||
|
'proc': Popen(['vlc', url])
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
|
||||||
|
@app.route('/stop/<id>')
|
||||||
|
def stream_stop(id):
|
||||||
|
if id in SESSIONS:
|
||||||
|
SESSIONS[id]['proc'].terminate()
|
||||||
|
return "true"
|
||||||
|
return "false"
|
||||||
|
|
||||||
|
def cleanup_thread():
|
||||||
|
while True:
|
||||||
|
dead_sessions = []
|
||||||
|
|
||||||
|
for session in SESSIONS.values():
|
||||||
|
if session['proc'].poll() != None:
|
||||||
|
# thread has terminated
|
||||||
|
dead_sessions.append(session['id'])
|
||||||
|
|
||||||
|
for id in dead_sessions:
|
||||||
|
url = SESSIONS[id]['url']
|
||||||
|
print(f"Removing dead session {id} for url {url}")
|
||||||
|
del SESSIONS[id]
|
||||||
|
|
||||||
|
sleep(10)
|
||||||
|
|
||||||
|
t = Thread(target=cleanup_thread)
|
||||||
|
t.start()
|
||||||
|
|
||||||
|
app.run(host='0.0.0.0', port=5005)
|
Loading…
Reference in New Issue