#!/usr/bin/env python3 import json import urllib.parse import re import os import streamlink import tornado.web import tornado.routing import requests providers = {} providers["nrk"] = "https://tv.nrk.no" providers["svt"] = "https://svtplay.se" providers["youtube"] = "https://www.youtube.com/watch?v=" providers["twitch"] = "https://twitch.tv" class ProxyElem(): def __init__(self, proxy): self.stream = streamlink.Streamlink() self.proxy = proxy self.req = {} self.stream.set_option("http-timeout", 2.0) if proxy is not None: self.stream.set_option("https-proxy", "socks5://" + proxy) self.stream.set_option("http-proxy", "socks5://" + proxy) self.req["http"] = "socks5://" + proxy self.req["https"] = "socks5://" + proxy def __repr__(self): return self.proxy proxies = {} for key in providers: proxies[key] = [] current = [] for i in range(0,9): proxy = os.environ.get(f'{key}_proxy{i}'.upper()) if proxy is not None: current.append(proxy) if len(current) == 0: proxies[key].append(ProxyElem(None)) else: for proxy in current: proxies[key].append(ProxyElem(proxy)) playlist = None icecast_server = os.environ.get("ICECAST_SERVER") stream_server = os.environ.get("STREAM_SERVER") proxy_server = os.environ.get("PROXY_SERVER") if icecast_server is not None and stream_server is not None: with open("/app/sources.json", "r") as f: data = json.loads(f.read()) playlist = "#EXTM3U\n" for key in data: current = data[key] name = current["name"] radio = current["radio"] if radio: playlist += f'#EXTINF:0 radio="true", {name}\n' playlist += icecast_server + key + "\n" else: playlist += f'#EXTINF:0 radio="false", {name}\n' playlist += stream_server + key + "\n" template = None videojs_version = None try: with open("/app/index.html", "r") as f: template = tornado.template.Template(f.read().strip()) with open("/app/videojs-version.txt", "r") as f: videojs_version = f.read().strip() except Exception as e: print(e) def get_proxy_url(proxy, current, path): data = {} data["upstream"] = urllib.parse.urljoin(current, path) data["proxied"] = True ret = None if proxy is None: data["proxied"] = False else: data["proxy"] = proxy if proxy_server is None: return data["upstream"] presp = requests.post(proxy_server, json=data) return presp.text def upstream_type(current, proxy): if proxy is None: resp = requests.head(current) else: resp = requests.head(current, proxies=proxy.req) return resp.headers.get("Content-Type", "binary/octet-stream") def rewrite(current, provider, proxy, ctype): resp = requests.get(current, proxies=proxy.req) ndata = None if resp.text is not None: links = [] for line in resp.text.splitlines(): if line.startswith("#EXT-X-KEY:METHOD="): matches = re.findall(r'(?<=URI=").+(?=")', line) if len(matches) == 1: ldata = {} ldata["upstream"] = urllib.parse.urljoin(current, matches[0]) ldata["proxy"] = proxy.proxy ldata["proxied"] = isinstance(proxy.proxy, str) links.append(ldata) elif line.startswith("#"): pass else: ldata = {} ldata["upstream"] = urllib.parse.urljoin(current, line) ldata["proxy"] = proxy.proxy ldata["proxied"] = isinstance(proxy.proxy, str) links.append(ldata) if isinstance(proxy_server, str): ndata = "" presp = requests.post(proxy_server, json=links) if isinstance(presp.text, str): links = json.loads(presp.text) for line in resp.text.splitlines(): if line.startswith("#EXT-X-KEY:METHOD="): matches = re.findall(r'(?<=URI=").+(?=")', line) if len(matches) == 1: new_url = links.pop(0) ndata += re.sub(r'URI=".+"', f'URI="{new_url}"', line) elif line.startswith("#"): ndata += line else: ndata += links.pop(0) ndata += "\n" return ndata class MainHandler(tornado.web.RequestHandler): def handle_any(self, write): provider = self.get_query_argument("provider", None) render = self.get_query_argument("render", False) if isinstance(provider, str): if isinstance(render, str): if render.lower() == "true": self.handle_render(provider, write) else: self.handle_stream(provider, write) else: self.handle_stream(provider, write) else: self.set_status(404) if write: self.write("Stream not found.") def handle_render(self, provider, write): if template is not None: stream_path = f'{self.request.path}?provider={provider}' stream_url = f'http://127.0.0.1:8080{stream_path}' ctype = upstream_type(stream_url, None) rendered = template.generate(stream=stream_path, content_type=ctype, videojs_version=videojs_version) self.write(rendered) else: self.set_status(404) self.write("HTML template missing.") def handle_stream(self, provider, write): upstream = None proxy = None if provider in providers.keys(): path = self.request.path if provider == "youtube": path = path.strip("/") src = providers[provider] + path proxy_list = None proxy_iter = None proxy_list_orig = proxies.get(provider) if isinstance(proxy_list_orig, list): proxy_list = proxy_list_orig.copy() proxy_iter = proxy_list_orig.copy() if isinstance(proxy_list, list): for i in proxy_iter: current_list = proxy_list.copy() current = proxy_list.pop() proxy_list = [current] + proxy_list try: resp = requests.head(src, allow_redirects=True, proxies=current.req, timeout=(0.3, 2.0)) if resp is not None: src = resp.url except Exception as e: print(e) else: proxies[provider] = current_list proxy = current break if proxy is not None: try: streams = proxy.stream.streams(src) for key in reversed(streams): stream = streams.get(key) if hasattr(stream, "url"): upstream = stream.url break except Exception as e: print(e) if upstream is None: self.set_status(404) if write: self.write("Stream not found.") else: ctype = upstream_type(upstream, proxy) data = None if "mpegurl" in ctype.lower(): data = rewrite(upstream, provider, proxy) else: ldata = {} ldata["upstream"] = upstream ldata["proxy"] = proxy.proxy ldata["proxied"] = isinstance(proxy.proxy, str) links = [ldata] resp = requests.post(proxy_server, json=links) if isinstance(resp.text, str): new_links = json.loads(resp.text) if isinstance(new_links, list) and len(new_links) == 1: upstream = new_links.pop() if data is None: self.redirect(upstream, status=303) else: self.set_header("Content-Type", "application/vnd.apple.mpegurl") self.write(data) def get(self): self.handle_any(True) def head(self): self.handle_any(False) class FileHandler(tornado.web.RequestHandler): def get(self): self.set_header("Content-Type", "text/plain; charset=utf-8") self.write(playlist) try: handlers = [] handlers.append((tornado.routing.PathMatches("/sources.m3u8"), FileHandler)) handlers.append((tornado.routing.AnyMatches(), MainHandler)) app_web = tornado.web.Application(handlers) app_web.listen(8080) tornado.ioloop.IOLoop.current().start() except KeyboardInterrupt: print()