load video from render
This commit is contained in:
parent
a605e7c43d
commit
5f56cf22ae
@ -47,37 +47,17 @@ class ProxyElem():
|
||||
return aiohttp.ClientSession(connector=connector, timeout=timeout)
|
||||
def __repr__(self):
|
||||
return str(self.proxy)
|
||||
async def content_type(self, url):
|
||||
cached = ctype_cache.get(url)
|
||||
if isinstance(cached, str) and "binary" not in cached.lower():
|
||||
return cached
|
||||
async with self.session() as session:
|
||||
for i in range(5):
|
||||
try:
|
||||
resp = await session.head(url)
|
||||
ctype = resp.headers.get("Content-Type", None)
|
||||
except Exception as e:
|
||||
logger.info(e)
|
||||
else:
|
||||
if isinstance(ctype, str):
|
||||
ctype_cache[url] = ctype
|
||||
return ctype
|
||||
return "binary/octet-type"
|
||||
async def proxy_url(self, current, path):
|
||||
async def proxy_url(self, urls):
|
||||
if not isinstance(proxy_server, str):
|
||||
return urls
|
||||
jdata = None
|
||||
data = {}
|
||||
data_list = [data]
|
||||
if path is None:
|
||||
data["upstream"] = current
|
||||
else:
|
||||
data["upstream"] = urllib.parse.urljoin(current, path)
|
||||
data["proxied"] = True
|
||||
if self.proxy is None:
|
||||
data["proxied"] = False
|
||||
else:
|
||||
data_list = []
|
||||
for url in urls:
|
||||
data = {}
|
||||
data["upstream"] = url
|
||||
data["proxy"] = self.proxy
|
||||
if proxy_server is None:
|
||||
return data["upstream"]
|
||||
data["proxied"] = isinstance(self.proxy, str)
|
||||
data_list.append(data)
|
||||
try:
|
||||
async with self.local() as session:
|
||||
resp = await session.post(proxy_server, json=data_list)
|
||||
@ -85,10 +65,16 @@ class ProxyElem():
|
||||
jdata = json.loads(text)
|
||||
except Exception as e:
|
||||
logger.info(e)
|
||||
if isinstance(jdata, list) and len(jdata) == 1:
|
||||
return jdata[0]
|
||||
if isinstance(jdata, list):
|
||||
ret_data = []
|
||||
for i in range(len(jdata)):
|
||||
if isinstance(urls[i], str):
|
||||
ret_data.append(jdata[i])
|
||||
else:
|
||||
ret_data.append(None)
|
||||
return ret_data
|
||||
else:
|
||||
return data["upstream"]
|
||||
return urls
|
||||
|
||||
class AsyncSessionData():
|
||||
def __init__(self, resp, current):
|
||||
@ -125,28 +111,18 @@ stream_providers.setup(proxy_keys)
|
||||
class UpstreamHandler():
|
||||
def __init__(self):
|
||||
self.provider = None
|
||||
self.render_url = None
|
||||
self.stream_url = None
|
||||
self.valid = False
|
||||
self.proxy = None
|
||||
self.upstream = None
|
||||
self.upstream_safe = None
|
||||
self.render = False
|
||||
self.stream = False
|
||||
async def setup(self, handler):
|
||||
self.provider = handler.get_query_argument("provider", None)
|
||||
render_str = handler.get_query_argument("render", "false")
|
||||
if self.provider in providers.keys():
|
||||
if render_str.lower() == "true":
|
||||
self.render = True
|
||||
else:
|
||||
self.stream = True
|
||||
|
||||
self.valid = True
|
||||
path = handler.request.path
|
||||
if self.provider == "nextcloud":
|
||||
path = path.removesuffix("/").removesuffix("download").removesuffix("/")
|
||||
elif self.provider == "youtube":
|
||||
path = path.removeprefix("/")
|
||||
|
||||
src = providers[self.provider] + path
|
||||
proxy_list = proxies.get(self.provider)
|
||||
if isinstance(proxy_list, list):
|
||||
@ -170,10 +146,8 @@ class UpstreamHandler():
|
||||
new_url = str(resp.url)
|
||||
if new_url.lower().startswith("https://consent.youtube.com"):
|
||||
self.upstream = src
|
||||
self.upstream_safe = urllib.parse.quote(src)
|
||||
else:
|
||||
self.upstream = new_url
|
||||
self.upstream_safe = urllib.parse.quote(new_url)
|
||||
self.proxy = result.current
|
||||
break
|
||||
for future in futures:
|
||||
@ -201,7 +175,7 @@ if icecast_server is not None and stream_server is not None:
|
||||
logger.info(e)
|
||||
|
||||
template_html = None
|
||||
script_file = None
|
||||
template_script = None
|
||||
videojs_version = None
|
||||
font_awesome_version = None
|
||||
custom_style = None
|
||||
@ -210,9 +184,7 @@ try:
|
||||
with open("/app/index.html", "r") as f:
|
||||
template_html = tornado.template.Template(f.read().strip())
|
||||
with open("/app/script.js", "r") as f:
|
||||
script_raw = bytes(f.read().strip(), "utf-8")
|
||||
b64 = str(base64.b64encode(script_raw), "ascii")
|
||||
script_file = f'data:text/javascript;charset=utf-8;base64,{b64}'
|
||||
template_script = tornado.template.Template(f.read().strip())
|
||||
with open("/app/version/video.js.txt", "r") as f:
|
||||
videojs_version = f.read().strip()
|
||||
with open("/app/version/chromecast.txt", "r") as f:
|
||||
@ -232,18 +204,31 @@ class MainHandler(tornado.web.RequestHandler):
|
||||
async def handle_any(self, redir):
|
||||
handler = UpstreamHandler()
|
||||
await handler.setup(self)
|
||||
if handler.render:
|
||||
if handler.valid:
|
||||
await self.handle_render(handler)
|
||||
elif handler.stream:
|
||||
await self.handle_stream(handler, redir)
|
||||
else:
|
||||
logger.info(f'provider missing {self.request.uri}')
|
||||
self.set_status(404)
|
||||
self.write("Stream not found. (provider missing)")
|
||||
|
||||
async def handle_render(self, handler):
|
||||
if script_file is not None and template_html is not None:
|
||||
provider_data = await stream_providers.get_any(handler.upstream, handler.proxy, logger)
|
||||
if template_script is not None and template_html is not None:
|
||||
provider_data = None
|
||||
if handler.provider == "nextcloud":
|
||||
provider_data = await stream_providers.get_nextcloud(handler.upstream, handler.proxy, logger)
|
||||
else:
|
||||
provider_data = await stream_providers.get_any(handler.upstream, handler.proxy, logger)
|
||||
proxied = await handler.proxy.proxy_url([provider_data.upstream(), provider_data.thumbnail()])
|
||||
|
||||
video_info = {}
|
||||
video_info["upstream"] = proxied[0]
|
||||
video_info["poster"] = proxied[1]
|
||||
video_info["ctype"] = provider_data.ctype()
|
||||
|
||||
script = template_script.generate(info=json.dumps(video_info))
|
||||
b64 = str(base64.b64encode(script), "ascii")
|
||||
script_file = f'data:text/javascript;charset=utf-8;base64,{b64}'
|
||||
|
||||
data["script"] = script_file
|
||||
data["videojs_version"] = videojs_version
|
||||
data["chromecast_version"] = chromecast_version
|
||||
@ -255,35 +240,6 @@ class MainHandler(tornado.web.RequestHandler):
|
||||
self.set_status(404)
|
||||
self.write("HTML template missing.")
|
||||
|
||||
async def handle_stream(self, handler, redir):
|
||||
upstream = None
|
||||
if handler.provider == "nextcloud":
|
||||
upstream = handler.upstream + "/download"
|
||||
else:
|
||||
provider_data = await stream_providers.get_any(handler.upstream, handler.proxy, logger)
|
||||
upstream = provider_data.upstream()
|
||||
if isinstance(provider_data.thumbnail(), str):
|
||||
image = await handler.proxy.proxy_url(provider_data.thumbnail(), None)
|
||||
if isinstance(image, str):
|
||||
self.set_header("Custom-Poster", image)
|
||||
if upstream is None:
|
||||
logger.info(f'invalid upstream ({handler.provider})')
|
||||
self.set_status(404)
|
||||
self.write("Stream not found. (invalid upstream)")
|
||||
else:
|
||||
upstream_proxy = await handler.proxy.proxy_url(upstream, None)
|
||||
logger.info(upstream_proxy)
|
||||
ctype = await handler.proxy.content_type(upstream_proxy)
|
||||
self.set_header("Content-Type", ctype)
|
||||
if redir:
|
||||
if ctype == "application/vnd.apple.mpegurl":
|
||||
async with handler.proxy.local() as session:
|
||||
resp = await session.get(upstream_proxy)
|
||||
data = await resp.read()
|
||||
self.write(data)
|
||||
else:
|
||||
self.redirect(upstream_proxy, status=303)
|
||||
|
||||
async def get(self):
|
||||
await self.handle_any(True)
|
||||
async def head(self):
|
||||
|
@ -4,6 +4,7 @@ import requests
|
||||
import asyncio
|
||||
import html.parser
|
||||
import expiringdict
|
||||
import json
|
||||
|
||||
streamlink_sessions = {}
|
||||
streamlink_default_session = streamlink.Streamlink()
|
||||
@ -41,10 +42,41 @@ class MetaParser(html.parser.HTMLParser):
|
||||
elif attr[0] == "property" and attr[1] in self.accepted_attrs:
|
||||
name = attr[1]
|
||||
|
||||
class NextcloudParser(html.parser.HTMLParser):
|
||||
def __init__(self):
|
||||
self.meta_data = {}
|
||||
super().__init__()
|
||||
def handle_starttag_meta(self, attrs):
|
||||
name = None
|
||||
for attr in (attrs + attrs):
|
||||
if len(attr) == 2:
|
||||
if isinstance(name, str):
|
||||
if attr[0] == "content":
|
||||
self.meta_data[name] = attr[1]
|
||||
return
|
||||
elif attr[0] == "property":
|
||||
name = attr[1]
|
||||
def handle_starttag_input(self, attrs):
|
||||
name = None
|
||||
for attr in (attrs + attrs):
|
||||
if len(attr) == 2:
|
||||
if isinstance(name, str):
|
||||
if attr[0] == "value":
|
||||
self.meta_data[name] = attr[1]
|
||||
return
|
||||
elif attr[0] == "name":
|
||||
name = attr[1]
|
||||
def handle_starttag(self, tag, attrs):
|
||||
if tag == "meta":
|
||||
return self.handle_starttag_meta(attrs)
|
||||
elif tag == "input":
|
||||
return self.handle_starttag_input(attrs)
|
||||
|
||||
class StreamData():
|
||||
def __init__(self, upstream, thumbnail, title, description, override):
|
||||
def __init__(self, upstream, ctype, thumbnail, title, description, override):
|
||||
self.values = {}
|
||||
self.values["upstream"] = upstream
|
||||
self.values["ctype"] = ctype
|
||||
self.values["thumbnail"] = thumbnail
|
||||
self.values["title"] = title
|
||||
self.values["description"] = description
|
||||
@ -56,6 +88,8 @@ class StreamData():
|
||||
self.values[key] = value
|
||||
def upstream(self):
|
||||
return self.values.get("upstream")
|
||||
def ctype(self):
|
||||
return self.values.get("ctype")
|
||||
def thumbnail(self):
|
||||
return self.values.get("thumbnail")
|
||||
def title(self):
|
||||
@ -83,10 +117,27 @@ class StreamProvider():
|
||||
proxy = str(proxy)
|
||||
if len(proxy) > 5:
|
||||
self.proxy = proxy
|
||||
def process(self):
|
||||
data = self.stream()
|
||||
if not isinstance(data.upstream(), str):
|
||||
return data
|
||||
proxies = None
|
||||
if isinstance(self.proxy, str):
|
||||
proxies = {}
|
||||
proxies["http"] = "socks5://" + self.proxy
|
||||
proxies["https"] = "socks5://" + self.proxy
|
||||
ctype = "binary/octet-stream"
|
||||
try:
|
||||
resp = requests.head(data.upstream(), proxies=proxies)
|
||||
except Exception as e:
|
||||
self.logger.info(e)
|
||||
else:
|
||||
ctype = resp.headers.get("Content-Type", "binary/octet-stream")
|
||||
return StreamData(data.upstream(), ctype, data.thumbnail(), data.title(), data.description(), data.override)
|
||||
async def run(self):
|
||||
data = None
|
||||
try:
|
||||
future = asyncio.to_thread(self.stream)
|
||||
future = asyncio.to_thread(self.process)
|
||||
data = await asyncio.wait_for(future, timeout=5)
|
||||
except Exception as e:
|
||||
self.logger.info(e)
|
||||
@ -106,10 +157,10 @@ class StreamlinkRunner(StreamProvider):
|
||||
for key in reversed(streams):
|
||||
stream = streams.get(key)
|
||||
if hasattr(stream, "url"):
|
||||
return StreamData(stream.url, None, None, None, False)
|
||||
return StreamData(stream.url, None, None, None, None, False)
|
||||
except Exception as e:
|
||||
self.logger.info(e)
|
||||
return StreamData(None, None, None, None, False)
|
||||
return StreamData(None, None, None, None, None, False)
|
||||
|
||||
class YoutubeRunner(StreamProvider):
|
||||
def stream(self):
|
||||
@ -153,7 +204,20 @@ class YoutubeRunner(StreamProvider):
|
||||
best_url = new_url
|
||||
except Exception as e:
|
||||
self.logger.info(e)
|
||||
return StreamData(best_url, thumbnail, title, description, True)
|
||||
print(json.dumps(best_format, indent=4))
|
||||
return StreamData(best_url, None, thumbnail, title, description, True)
|
||||
|
||||
class NextcloudRunner(StreamProvider):
|
||||
def stream(self):
|
||||
data = {}
|
||||
try:
|
||||
resp = requests.get(self.upstream)
|
||||
parser = NextcloudParser()
|
||||
parser.feed(resp.text)
|
||||
data = parser.meta_data
|
||||
except Exception as e:
|
||||
self.logger.info(e)
|
||||
return StreamData(data.get("downloadURL"), data.get("mimetype"), None, data.get("og:title"), data.get("og:description"), False)
|
||||
|
||||
class MetaRunner(StreamProvider):
|
||||
def stream(self):
|
||||
@ -165,7 +229,7 @@ class MetaRunner(StreamProvider):
|
||||
data = parser.meta_data
|
||||
except Exception as e:
|
||||
self.logger.info(e)
|
||||
return StreamData(None, data.get("og:image"), data.get("og:title"), data.get("og:description"), False)
|
||||
return StreamData(None, None, data.get("og:image"), data.get("og:title"), data.get("og:description"), False)
|
||||
|
||||
upstream_cache = expiringdict.ExpiringDict(max_len=512, max_age_seconds=1800)
|
||||
|
||||
@ -192,8 +256,11 @@ async def get_ytdl(upstream, proxy, logger):
|
||||
async def get_meta(upstream, proxy, logger):
|
||||
return await get_from_runner((2, upstream), MetaRunner(upstream, proxy, logger), logger)
|
||||
|
||||
async def get_nextcloud(upstream, proxy, logger):
|
||||
return await get_from_runner((3, upstream), NextcloudRunner(upstream, proxy, logger), logger)
|
||||
|
||||
async def get_any(upstream, proxy, logger):
|
||||
cache_key = (3, upstream)
|
||||
cache_key = (4, upstream)
|
||||
cached = upstream_cache.get(cache_key)
|
||||
if isinstance(cached, StreamData):
|
||||
return cached
|
||||
@ -201,7 +268,8 @@ async def get_any(upstream, proxy, logger):
|
||||
tasks.append(asyncio.create_task(get_streamlink(upstream, proxy, logger)))
|
||||
tasks.append(asyncio.create_task(get_ytdl(upstream, proxy, logger)))
|
||||
tasks.append(asyncio.create_task(get_meta(upstream, proxy, logger)))
|
||||
result = StreamData(None, None, None, None, False)
|
||||
|
||||
result = StreamData(None, None, None, None, None, False)
|
||||
for task in asyncio.as_completed(tasks):
|
||||
temp_result = await task
|
||||
if isinstance(temp_result, StreamData):
|
||||
|
@ -12,6 +12,4 @@
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/{{ data["font_awesome_version"] }}/css/all.min.css">
|
||||
<link rel="stylesheet" href="{{ data["custom_style"] }}">
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,33 +1,10 @@
|
||||
(() => {
|
||||
const findUpstreamVideo = () => {
|
||||
const search = new URLSearchParams(location.search);
|
||||
search.set("render", "false");
|
||||
const url = new URL(location.origin);
|
||||
url.pathname = location.pathname;
|
||||
url.search = search.toString();
|
||||
return url.href;
|
||||
}
|
||||
|
||||
const upstream = findUpstreamVideo();
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("HEAD", upstream, true);
|
||||
xhr.send();
|
||||
|
||||
let count = 2;
|
||||
const handleCount = () => {
|
||||
if(--count === 0) {
|
||||
handle();
|
||||
}
|
||||
}
|
||||
|
||||
const info = {% raw info %};
|
||||
const handle = () => {
|
||||
const [body] = document.getElementsByTagName("body");
|
||||
const video = document.createElement("video");
|
||||
video.className = "video-js vjs-big-play-centered";
|
||||
body.appendChild(video);
|
||||
const ctype = xhr.getResponseHeader("Content-Type");
|
||||
const image = xhr.getResponseHeader("Custom-Poster");
|
||||
console.log(ctype);
|
||||
const options = {};
|
||||
options.controls = true;
|
||||
options.liveui = true;
|
||||
@ -38,14 +15,14 @@
|
||||
options.plugins.chromecast = {};
|
||||
options.plugins.chromecast.addButtonToControlBar = false;
|
||||
const player = videojs(video, options);
|
||||
if((image instanceof String) || ((typeof image) == "string")) {
|
||||
player.poster(image);
|
||||
if((info.poster instanceof String) || ((typeof info.poster) == "string")) {
|
||||
player.poster(info.poster);
|
||||
}
|
||||
const source = {};
|
||||
source.type = ctype;
|
||||
source.src = upstream;
|
||||
source.type = info.ctype;
|
||||
source.src = info.upstream;
|
||||
player.src(source);
|
||||
const canPlayTypeRaw = player.canPlayType(ctype);
|
||||
const canPlayTypeRaw = player.canPlayType(info.ctype);
|
||||
const canPlayType = (canPlayTypeRaw === "maybe") || (canPlayTypeRaw === "probably");
|
||||
if(canPlayType) {
|
||||
const Button = videojs.getComponent("Button");
|
||||
@ -84,6 +61,5 @@
|
||||
});
|
||||
}
|
||||
}
|
||||
document.addEventListener("DOMContentLoaded", handleCount);
|
||||
xhr.addEventListener("load", handleCount);
|
||||
document.addEventListener("DOMContentLoaded", handle);
|
||||
})();
|
||||
|
Loading…
Reference in New Issue
Block a user