]> git.gir.st - subscriptionfeed.git/blob - app/proxy/__init__.py
enable hls proxying, simplify /api/hls_* munging, format code
[subscriptionfeed.git] / app / proxy / __init__.py
1 import re
2 import requests, requests_cache
3 from flask import Flask, Blueprint, request, Response
4 from flask_login import current_user
5
6 frontend = Blueprint('proxy', __name__)
7
8 def app():
9 app = Flask(__name__)
10 app.register_blueprint(frontend)
11 return app
12
13 # https://github.com/psf/requests/issues/5612
14 def silence_errors(generator):
15 try:
16 yield from generator
17 except requests.exceptions.RequestException:
18 return None
19
20 @frontend.route("/videoplayback/<path:path>")
21 @frontend.route("/videoplayback")
22 def videoplayback(path=None):
23 if path is not None: # seg.ts URL
24 args = path.split("/")[0:-1]
25 host = dict(zip(args[::2], args[1::2])).get('hls_chunk_host')
26 path = "/" + path.replace(';','%3B').replace('=','%3D')
27 else:
28 fvip = request.args.get("fvip", "3")
29 mn = request.args.get("mn", "").split(",")[-1]
30 host = f"r{fvip}---{mn}.googlevideo.com"
31 path = ""
32
33 REQUEST_HEADERS_WHITELIST = {
34 "accept",
35 "accept-encoding",
36 "cache-control",
37 "range",
38 }
39 RESPONSE_HEADERS_WHITELIST = {
40 "accept-ranges",
41 "cache-control",
42 "content-length",
43 "content-range",
44 "content-type",
45 "expires",
46 "x-content-type-options",
47 }
48
49 req_headers = {
50 k:v for k,v in request.headers
51 if k.lower() in REQUEST_HEADERS_WHITELIST
52 }
53 with requests_cache.disabled():
54 r = requests.get(f"https://{host}/videoplayback{path}",
55 request.args.to_dict(),
56 headers=req_headers,
57 stream=True)
58
59 resp_headers = {
60 k:v for k,v in r.headers.items()
61 if k.lower() in RESPONSE_HEADERS_WHITELIST
62 }
63 response = Response(
64 silence_errors(r.iter_content(chunk_size=8192)),
65 status=r.status_code, headers=resp_headers)
66 response.call_on_close(lambda: r.close())
67 response.headers['Access-Control-Allow-Origin'] = cors_origin()
68 return response
69
70 @frontend.route("/api/manifest/hls_playlist/<path:path>")
71 @frontend.route("/api/manifest/hls_variant/<path:path>")
72 def hls_manifest(path):
73 path_fixed = request.path.replace(';','%3B').replace('=','%3D')
74 with requests_cache.disabled():
75 r = requests.get(f"https://manifest.googlevideo.com{path_fixed}")
76 if not r.ok:
77 return "", r.status_code
78
79 rv = re.sub(r"^https://[a-z0-9-]+\.googlevideo\.com", "", r.text, flags=re.M)
80 return rv, {
81 'Content-Type': 'application/x-mpegURL',
82 'Access-Control-Allow-Origin': cors_origin()
83 }
84
85 def cors_origin():
86 return f"{request.environ.get('wsgi.url_scheme')}://{request.host}"
87
88 @frontend.route("/x-hls/")
89 @frontend.route("/x-hls/<vid>")
90 def xxx(vid="5qap5aO4i9A"):
91 from ..common.common import get_video_info
92 _, map, _, _, _ = get_video_info(vid, -1)
93 url = map['hlsManifestUrl'].replace("https://manifest.googlevideo.com", "")
94 return f"""
95 <!DOCTYPE html>
96 <meta charset="utf-8"/>
97 <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
98 <style>
99 video {{ width: 640px; height: 360px; }}
100 </style>
101 <video autoplay preload="none" controls="true">
102 <source src="{url}" type="application/x-mpegURL"/>
103 </video>
104 <script>
105 let vid = document.querySelector('video');
106 if (!vid.canPlayType('application/vnd.apple.mpegurl') && Hls.isSupported()) {{
107 var hls = new Hls();
108 hls.loadSource(document.querySelector("source").src);
109 hls.attachMedia(vid);
110 }}
111 </script>
112 """
113
114
115 if __name__ == '__main__':
116 app().run(debug=True)
Imprint / Impressum