ydl.dpiView |
---|
| 1 … | +#!/usr/bin/env python3 |
| 2 … | + |
| 3 … | +# © 2018 cel @f/6sQ6d2CMxRUhLpspgGIulDxDCwYD7DzFzPNr7u5AU=.ed25519 |
| 4 … | +# Copying and distribution of this file, with or without modification, |
| 5 … | +# are permitted in any medium without royalty provided the copyright |
| 6 … | +# notice and this notice are preserved. This file is offered as-is, |
| 7 … | +# without any warranty. |
| 8 … | + |
| 9 … | +import socket |
| 10 … | +import sys |
| 11 … | +import re |
| 12 … | +import threading |
| 13 … | +import youtube_dl |
| 14 … | +import urllib.parse |
| 15 … | +import html |
| 16 … | +import traceback |
| 17 … | +import json |
| 18 … | + |
| 19 … | +authRe = re.compile("<cmd='auth' msg='([^']*)' '>") |
| 20 … | +openRe = re.compile("<cmd='open_url' url='(.*?)' '>") |
| 21 … | +byeRe = re.compile("<cmd='DpiBye' '>") |
| 22 … | + |
| 23 … | +print("[ydl]: init", file=sys.stderr) |
| 24 … | + |
| 25 … | +def writeHeader(conn, url, contentType): |
| 26 … | + conn.send(("<cmd='start_send_page' url='" + url + "' '>").encode('utf-8')) |
| 27 … | + conn.send(('Content-type: ' + contentType + '\r\n\r\n').encode('utf-8')) |
| 28 … | + |
| 29 … | +def serveQuit(conn, url): |
| 30 … | + writeHeader(conn, url, "text/plain") |
| 31 … | + conn.send(b"quit") |
| 32 … | + conn.shutdown(socket.SHUT_WR) |
| 33 … | + print("[ydl]: quit") |
| 34 … | + exit() |
| 35 … | + |
| 36 … | +def serveHome(conn, url, uri): |
| 37 … | + writeHeader(conn, url, "text/html") |
| 38 … | + conn.send(("<!doctype html>" + |
| 39 … | + "<html>" + |
| 40 … | + "<head>" + |
| 41 … | + "<title>ydl</title></head>" + |
| 42 … | + "</head><body>\n" + |
| 43 … | + "<div>youtube_dl " + youtube_dl.version.__version__ + "</div>" + |
| 44 … | + "<div><a href=/quit>quit</a></div>" + |
| 45 … | + "</body></html>").encode('utf-8')) |
| 46 … | + conn.shutdown(socket.SHUT_WR) |
| 47 … | + |
| 48 … | +def serveEcho(conn, url, uri): |
| 49 … | + writeHeader(conn, url, "text/plain") |
| 50 … | + conn.send(str(uri).encode('utf-8')) |
| 51 … | + conn.shutdown(socket.SHUT_WR) |
| 52 … | + |
| 53 … | +def renderThumbnail(thumb): |
| 54 … | + url = thumb.get('url') |
| 55 … | + href = re.sub(r"default", "hqdefault", url) |
| 56 … | + return ("<a href='" + html.escape(href) + "'>" + |
| 57 … | + "<img src='" + html.escape(url) + "'" + |
| 58 … | + " alt='" + thumb.get('id', '') + "'></a>") |
| 59 … | + |
| 60 … | +def formatSize(size): |
| 61 … | + size = int(size) |
| 62 … | + if size < 1024: return size + ' B' |
| 63 … | + size /= 1024 |
| 64 … | + if size < 1024: return '%.2f KB' % size |
| 65 … | + size /= 1024 |
| 66 … | + if size < 1024: return '%.2f MB' % size |
| 67 … | + return '%.2f GB' % size |
| 68 … | + |
| 69 … | +def renderFormat(form): |
| 70 … | + id = form.get('format_id') |
| 71 … | + ext = form.get('ext') |
| 72 … | + name = form.get('format') |
| 73 … | + url = form.get('url') |
| 74 … | + resolution = form.get('resolution') |
| 75 … | + vcodec = form.get('vcodec') |
| 76 … | + acodec = form.get('acodec') |
| 77 … | + filesize = form.get('filesize') |
| 78 … | + return ("<tr><td>" + |
| 79 … | + ('<a href="' + html.escape(url) + '">' if url else "") + |
| 80 … | + html.escape(id) + |
| 81 … | + ('</a>' if url else "") + |
| 82 … | + "</td>" + |
| 83 … | + "<td>" + (html.escape(str(ext)) if ext else "") + "</td>" + |
| 84 … | + "<td align=right>" + (str(formatSize(filesize)) if filesize else "") + "</td>" + |
| 85 … | + "<td>" + (html.escape(str(name)) if name else "") + "</td>" + |
| 86 … | + "<td>" + (html.escape(str(resolution)) if resolution else "") + "</td>" + |
| 87 … | + "<td>" + (html.escape(str(acodec)) if acodec else "") + "</td>" + |
| 88 … | + "<td>" + (html.escape(str(vcodec)) if vcodec else "") + "</td>" + |
| 89 … | + "<!--<td><pre>" + json.dumps(form) + "</pre></td>-->") |
| 90 … | + |
| 91 … | +def serveResult(conn, origUrl, res): |
| 92 … | + writeHeader(conn, origUrl, "text/html") |
| 93 … | + title = res.get('title') |
| 94 … | + webpage_url = res.get('webpage_url') |
| 95 … | + thumbnails = res.get('thumbnails', []) |
| 96 … | + formats = res.get('formats', []) |
| 97 … | + uploader = res.get('uploader') |
| 98 … | + description = res.get('description') |
| 99 … | + if formats: del res['formats'] |
| 100 … | + conn.send(("<!doctype html>" + |
| 101 … | + "<html>" + |
| 102 … | + "<head>" + |
| 103 … | + "<title>" + html.escape(title) + "</title></head>" + |
| 104 … | + "</head><body>\n" + |
| 105 … | + "<h1>" + |
| 106 … | + ("<a href='" + html.escape(webpage_url) + "'>" if webpage_url else "") + |
| 107 … | + html.escape(title) + |
| 108 … | + ("</a>" if webpage_url else "") + |
| 109 … | + "</h1>\n" + |
| 110 … | + ("<div>" + |
| 111 … | + " ".join([renderThumbnail(thumb) + "\n" for thumb in thumbnails]) + |
| 112 … | + "</div>\n" if len(thumbnails) > 0 else "") + |
| 113 … | + ("<div>" + html.escape(uploader) + "</div>" if uploader else "") + |
| 114 … | + ("<p>" + html.escape(description) + "</p>" if description else "") + |
| 115 … | + ("<table>" + |
| 116 … | + "<thead>" + |
| 117 … | + "<tr><th>code</th><th>ext</th><th>size</th><th>format</th><th>resolution</th><th>audio</th><th>video</th></tr>" + |
| 118 … | + "</thead>\n<tbody>" + |
| 119 … | + "\n".join([renderFormat(form) + "\n" for form in formats]) + |
| 120 … | + "</tbody></table>\n" if len(thumbnails) > 0 else "") + |
| 121 … | + "<!--<pre>" + json.dumps(res, indent=True) + "</pre>-->\n" + |
| 122 … | + "</body></html>").encode('utf-8')) |
| 123 … | + conn.shutdown(socket.SHUT_WR) |
| 124 … | + |
| 125 … | +def serveOpen(conn, url): |
| 126 … | + uri = urllib.parse.urlparse(url) |
| 127 … | + if uri.path == "" or uri.path == "/": return serveHome(conn, url, uri) |
| 128 … | + if uri.scheme == "dpi": return serveEcho(conn, url, uri) |
| 129 … | + serveResource(conn, url, url[4:]) |
| 130 … | + |
| 131 … | +def serveResource(conn, origUrl, url): |
| 132 … | + try: |
| 133 … | + res = ydl.extract_info(url, download=False) |
| 134 … | + except: |
| 135 … | + etype, value, tb = sys.exc_info() |
| 136 … | + writeHeader(conn, origUrl, "text/plain") |
| 137 … | + lines = traceback.format_exception(etype, value, tb) |
| 138 … | + conn.send((''.join(lines)).encode('utf-8')) |
| 139 … | + conn.shutdown(socket.SHUT_WR) |
| 140 … | + else: |
| 141 … | + serveResult(conn, origUrl, res) |
| 142 … | + |
| 143 … | +def handleConn(conn, addr): |
| 144 … | + buf = "" |
| 145 … | + while True: |
| 146 … | + data = conn.recv(1024) |
| 147 … | + if not data: break |
| 148 … | + buf += data.decode('utf-8') |
| 149 … | + |
| 150 … | + while len(buf) > 0: |
| 151 … | + if buf[0] != '<': |
| 152 … | + conn.close() |
| 153 … | + return |
| 154 … | + |
| 155 … | + m = authRe.match(buf) |
| 156 … | + if m: |
| 157 … | + buf = buf[m.end():] |
| 158 … | + continue |
| 159 … | + |
| 160 … | + m = openRe.match(buf) |
| 161 … | + if m: |
| 162 … | + buf = buf[m.end():] |
| 163 … | + url = m.group(1) |
| 164 … | + if url == "ydl:/quit": return serveQuit(conn, url) |
| 165 … | + threading.Thread(target=serveOpen, args=(conn, url)).start() |
| 166 … | + return |
| 167 … | + |
| 168 … | + m = byeRe.match(buf) |
| 169 … | + if m: |
| 170 … | + print("[ydl]: bye") |
| 171 … | + exit() |
| 172 … | + |
| 173 … | +stdin = socket.fromfd(sys.stdin.fileno(), socket.AF_INET, socket.SOCK_STREAM) |
| 174 … | + |
| 175 … | +ydl = youtube_dl.YoutubeDL({ |
| 176 … | + 'no_color': True |
| 177 … | +}) |
| 178 … | + |
| 179 … | +while True: |
| 180 … | + try: |
| 181 … | + conn, addr = stdin.accept() |
| 182 … | + except KeyboardInterrupt: |
| 183 … | + exit() |
| 184 … | + else: |
| 185 … | + handleConn(conn, addr) |