#!/usr/bin/python """ Livestreamer Deamon """ import os import sys import time import atexit import re from signal import SIGTERM from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler from SocketServer import ThreadingMixIn #from livestreamer import Livestreamer from urllib import unquote, quote import urllib,urlparse import requests from requests.packages.urllib3.exceptions import InsecureRequestWarning requests.packages.urllib3.disable_warnings(InsecureRequestWarning) HOST_NAME = "" PORT_NUMBER = 88 LIVESTREAMER = None headers2dict = lambda h: dict([l.strip().split(": ") for l in h.strip().splitlines()]) sessions = {} class StreamHandler(BaseHTTPRequestHandler): def do_HEAD(self): self.send_response(200) self.send_header("Server", "StreamProxy") self.send_header("Content-type", "text/html") self.end_headers() def do_GET(self): """Respond to a GET request.""" #self.send_header("Connection", "keep-alive") #Content-Type: video/mp4 #self.end_headers() # http://localhost:88/hls?u=url.com/aaa/bbb.m3u8&h=headers&d=data # http://localhost:88/http://walterebert.com/playground/video/hls/ts/480x270.m3u8?token=xxxx|User-Agent=Enigma2|Cookie=xxxxx p = self.path.split("~") url = urllib.unquote(p[0][1:]) headers = headers2dict(""" User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36 """) if len(p)>1: for h in p[1:]: headers[h.split("=")[0]]=urllib.unquote(h.split("=")[1]) #self.fetch_offline(self.wfile) try: self.fetch_url(self.wfile, url, headers) except Exception as e: print "Got Exception: ", str(e) def fetch_offline(self,wfile): self.send_response(200) self.send_header("Server", "StreamProxy") self.send_header("Content-type", "video/mp4") self.end_headers() self.wfile.write(open("offline.mp4", "rb").read()) self.wfile.close() def fetch_url(self, wfile, url, headers): self.log_message("fetch_url: %s", url) #self.log_message("headers: %s", headers) base_url = "/".join(url.split("/")[0:-1]) if base_url not in sessions: sessions[base_url] = requests.Session() sessions[base_url].headers.update(headers) #print "request headers: ",sessions[base_url].headers r = sessions[base_url].get(url, stream=True,verify=False) self.send_response(int(r.status_code)) self.send_header("Content-Type",r.headers["Content-Type"]) for h in r.headers: if h in ("Content-Type","Server","Set-Cookie"):continue self.send_header(h, r.headers[h]) #print h,"=",r.headers[h] self.end_headers() if r.status_code == 200: for chunk in r.iter_content(4096): try: wfile.write(chunk) except Exception as e: print "Exception: ", str(e) pass #print ".", #self.wfile.close() #print " url streaming finished" else: self.send_response(200) class ThreadedHTTPServer(ThreadingMixIn, HTTPServer): """Handle requests in a separate thread.""" def start(): #global LIVESTREAMER #LIVESTREAMER = Livestreamer() httpd = ThreadedHTTPServer((HOST_NAME, PORT_NUMBER), StreamHandler) print time.asctime(), "Server Starts - %s:%s" % (HOST_NAME, PORT_NUMBER) try: httpd.serve_forever() except KeyboardInterrupt: pass httpd.server_close() print time.asctime(), "Server Stops - %s:%s" % (HOST_NAME, PORT_NUMBER) class Daemon: """ A generic daemon class. Usage: subclass the Daemon class and override the run() method """ def __init__(self, pidfile, stdin="/dev/null", stdout="/dev/null", stderr="/dev/null"): self.stdin = stdin self.stdout = stdout self.stderr = stderr self.pidfile = pidfile def daemonize(self): """ do the UNIX double-fork magic, see Stevens' "Advanced Programming in the UNIX Environment" for details (ISBN 0201563177) http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16 """ try: pid = os.fork() if pid > 0: # exit first parent sys.exit(0) except OSError, e: sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror)) sys.exit(1) # decouple from parent environment os.chdir("/") os.setsid() os.umask(0) # do second fork try: pid = os.fork() if pid > 0: # exit from second parent sys.exit(0) except OSError, e: sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror)) sys.exit(1) # redirect standard file descriptors sys.stdout.flush() sys.stderr.flush() si = file(self.stdin, "r") so = file(self.stdout, "a+") se = file(self.stderr, "a+", 0) os.dup2(si.fileno(), sys.stdin.fileno()) os.dup2(so.fileno(), sys.stdout.fileno()) os.dup2(se.fileno(), sys.stderr.fileno()) # write pidfile atexit.register(self.delpid) pid = str(os.getpid()) file(self.pidfile,"w+").write("%s\n" % pid) def delpid(self): os.remove(self.pidfile) def start(self): """ Start the daemon """ # Check for a pidfile to see if the daemon already runs try: pf = file(self.pidfile,"r") pid = int(pf.read().strip()) pf.close() except IOError: pid = None if pid: message = "pidfile %s already exist. Daemon already running?\n" sys.stderr.write(message % self.pidfile) sys.exit(1) # Start the daemon self.daemonize() self.run() def stop(self): """ Stop the daemon """ # Get the pid from the pidfile try: pf = file(self.pidfile,"r") pid = int(pf.read().strip()) pf.close() except IOError: pid = None if not pid: message = "pidfile %s does not exist. Daemon not running?\n" sys.stderr.write(message % self.pidfile) return # not an error in a restart # Try killing the daemon process try: while 1: os.kill(pid, SIGTERM) time.sleep(0.1) except OSError, err: err = str(err) if err.find("No such process") > 0: if os.path.exists(self.pidfile): os.remove(self.pidfile) else: print str(err) sys.exit(1) def restart(self): """ Restart the daemon """ self.stop() self.start() def run(self): """ You should override this method when you subclass Daemon. It will be called after the process has been daemonized by start() or restart(). """ class ProxyDaemon(Daemon): def run(self): start() if __name__ == "__main__": daemon = ProxyDaemon("/var/run/streamproxy.pid") if len(sys.argv) == 2: if "start" == sys.argv[1]: daemon.start() elif "stop" == sys.argv[1]: daemon.stop() elif "restart" == sys.argv[1]: daemon.restart() elif "manualstart" == sys.argv[1]: start() else: print "Unknown command" sys.exit(2) sys.exit(0) else: print "usage: %s start|stop|restart|manualstart" % sys.argv[0] sys.exit(2)