#!/usr/bin/env python3 # © 2018 cel @f/6sQ6d2CMxRUhLpspgGIulDxDCwYD7DzFzPNr7u5AU=.ed25519 # Copying and distribution of this file, with or without modification, # are permitted in any medium without royalty provided the copyright # notice and this notice are preserved. This file is offered as-is, # without any warranty. import socket import sys import re import threading import youtube_dl import urllib.parse import html import traceback import json authRe = re.compile("") openRe = re.compile("") byeRe = re.compile("") print("[ytdl]: init", file=sys.stderr) def writeHeader(conn, url, contentType): conn.send(("").encode('utf-8')) conn.send(('Content-type: ' + contentType + '\r\n\r\n').encode('utf-8')) def serveQuit(conn, url): writeHeader(conn, url, "text/plain") conn.send(b"quit") conn.shutdown(socket.SHUT_WR) print("[ytdl]: quit") exit() def serveHome(conn, url, uri): writeHeader(conn, url, "text/html") conn.send(("" + "" + "" + "ytdl" + "\n" + "
youtube_dl " + youtube_dl.version.__version__ + "
" + "
quit
" + "").encode('utf-8')) conn.shutdown(socket.SHUT_WR) def serveEcho(conn, url, uri): writeHeader(conn, url, "text/plain") conn.send(str(uri).encode('utf-8')) conn.shutdown(socket.SHUT_WR) def renderThumbnail(thumb): url = thumb.get('url') href = re.sub(r"default", "hqdefault", url) return ("" + "" + thumb.get(") def formatSize(size): size = int(size) if size < 1024: return size + ' B' size /= 1024 if size < 1024: return '%.2f KB' % size size /= 1024 if size < 1024: return '%.2f MB' % size return '%.2f GB' % size def renderFormat(form): id = form.get('format_id') ext = form.get('ext') name = form.get('format') url = form.get('url') resolution = form.get('resolution') vcodec = form.get('vcodec') acodec = form.get('acodec') filesize = form.get('filesize') return ("" + ('' if url else "") + html.escape(id) + ('' if url else "") + "" + "" + (html.escape(str(ext)) if ext else "") + "" + "" + (str(formatSize(filesize)) if filesize else "") + "" + "" + (html.escape(str(name)) if name else "") + "" + "" + (html.escape(str(resolution)) if resolution else "") + "" + "" + (html.escape(str(acodec)) if acodec else "") + "" + "" + (html.escape(str(vcodec)) if vcodec else "") + "" + "") def renderEntry(entry): thumbnails = entry.get('thumbnails', []) formats = entry.get('formats', []) uploader = entry.get('uploader') description = entry.get('description') duration = entry.get('duration') view_count = entry.get('view_count') if formats: del entry['formats'] tags = entry.get('tags') return ( ("
" + " ".join([renderThumbnail(thumb) + "\n" for thumb in thumbnails]) + "
\n" if len(thumbnails) > 0 else "") + ("
" + html.escape(uploader) + "
" if uploader else "") + ("

" + html.escape(description) + "

" if description else "") + ("

Duration: " + html.escape(str(duration)) + "

" if duration else "") + ("

View count: " + html.escape(str(view_count)) + "

" if view_count else "") + ("

Tags: " + ", ".join([html.escape(str(tag)) for tag in tags]) + "

" if tags else "") + ("" + "" + "" + "\n" + "\n".join([renderFormat(form) + "\n" for form in formats]) + "
codeextsizeformatresolutionaudiovideo
\n" if len(thumbnails) > 0 else "")) def serveResult(conn, origUrl, res): writeHeader(conn, origUrl, "text/html") title = res.get('title') webpage_url = res.get('webpage_url') entries = res.get('entries', []) if res.get('_type', None) == 'playlist' else [res] conn.send(("" + "" + "" + "" + html.escape(title) + "" + "\n" + "

" + ("" if webpage_url else "") + html.escape(title) + ("" if webpage_url else "") + "

\n" + "\n".join([renderEntry(entry) for entry in entries]) + "\n" + "").encode('utf-8')) conn.shutdown(socket.SHUT_WR) def serveOpen(conn, url): uri = urllib.parse.urlparse(url) if uri.scheme == "dpi": return serveEcho(conn, url, uri) if uri.netloc == "" and (uri.path == "" or uri.path == "/"): return serveHome(conn, url, uri) if uri.scheme == "ytdl" or uri.scheme == "ydl": if '.' in uri.netloc: return serveResource(conn, url, urllib.parse.urlunparse(( 'https', uri.netloc, uri.path, uri.params, uri.query, uri.fragment ))) return serveResource(conn, url, re.sub('yt?dl:(//)?', '', url)) return serveEcho(conn, url, uri) def serveResource(conn, origUrl, url): try: res = ytdl.extract_info(url, download=False) except: etype, value, tb = sys.exc_info() writeHeader(conn, origUrl, "text/plain") lines = traceback.format_exception(etype, value, tb) conn.send((''.join(lines)).encode('utf-8')) conn.shutdown(socket.SHUT_WR) else: serveResult(conn, origUrl, res) def handleConn(conn, addr): buf = "" while True: data = conn.recv(1024) if not data: break buf += data.decode('utf-8') while len(buf) > 0: if buf[0] != '<': conn.close() return m = authRe.match(buf) if m: buf = buf[m.end():] continue m = openRe.match(buf) if m: buf = buf[m.end():] url = m.group(1) if url == "ytdl:/quit": return serveQuit(conn, url) threading.Thread(target=serveOpen, args=(conn, url)).start() return m = byeRe.match(buf) if m: print("[ytdl]: bye") exit() stdin = socket.fromfd(sys.stdin.fileno(), socket.AF_INET, socket.SOCK_STREAM) ytdl = youtube_dl.YoutubeDL({ 'no_color': True }) while True: try: conn, addr = stdin.accept() except KeyboardInterrupt: exit() else: handleConn(conn, addr)