git ssb

0+

cel / dillo-ytdl



Commit 498e3731c8cc2a4d9c000c5050c68465560ed39d

Rename to dillo-ytdl

ytdl:// is consistent with mpv
cel committed on 4/28/2020, 2:46:30 PM
Parent: 29bace3450fcbaee33956eb93d25774d5485e71b

Files changed

README.mdchanged
ydl.dpideleted
ytdl.dpiadded
README.mdView
@@ -1,20 +1,20 @@
1-# dillo-ydl
1 +# dillo-ytdl
22
33 [youtube-dl][] plugin for [Dillo][].
44
5-It lets you visit URLs of the form "ydl:<url>" where `<url>` is the URL or video ID you would pass to `youtube-dl`, and you will get a page showing some info about the video, including a table of formats (as you would get from `youtube-dl -F`) with a link to download each one.
5 +It lets you visit URLs of the form "ytdl:<url>" where `<url>` is the URL or video ID you would pass to `youtube-dl`, and you will get a page showing some info about the video, including a table of formats (as you would get from `youtube-dl -F`) with a link to download each one.
66
77 ## Install
88
99 ```sh
10-git clone ssb://%oEzgqM849P03lR3tR7qwbjzw/c+R+v0oIoHyDgEDrbw=.sha256 dillo-ydl
11-cd dillo-ydl
10 +git clone ssb://%oEzgqM849P03lR3tR7qwbjzw/c+R+v0oIoHyDgEDrbw=.sha256 dillo-ytdl
11 +cd dillo-ytdl
1212 pip install -r requirements.txt # or sudo apt-get install youtube-dl
13-mkdir -p ~/.dillo/dpi/ydl
14-cp ydl.dpi ~/.dillo/dpi/ydl/ # or ln -rs ydl.dpi ~/.dillo/dpi/ydl/
13 +mkdir -p ~/.dillo/dpi/ytdl
14 +cp ytdl.dpi ~/.dillo/dpi/ytdl/ # or ln -rs ytdl.dpi ~/.dillo/dpi/ytdl/
1515 test -f ~/.dillo/dpidrc || cp /etc/dillo/dpidrc ~/.dillo/dpidrc
16-echo 'proto.ydl=ydl/ydl.dpi' >> ~/.dillo/dpidrc
16 +echo 'proto.ytdl=ytdl/ytdl.dpi' >> ~/.dillo/dpidrc
1717 dpidc stop
1818 ```
1919
2020 [youtube-dl]: https://rg3.github.io/youtube-dl/
ydl.dpiView
@@ -1,197 +1,0 @@
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 renderEntry(entry):
92- thumbnails = entry.get('thumbnails', [])
93- formats = entry.get('formats', [])
94- uploader = entry.get('uploader')
95- description = entry.get('description')
96- duration = entry.get('duration')
97- view_count = entry.get('view_count')
98-
99- if formats: del entry['formats']
100- tags = entry.get('tags')
101- return (
102- ("<div>" +
103- " ".join([renderThumbnail(thumb) + "\n" for thumb in thumbnails]) +
104- "</div>\n" if len(thumbnails) > 0 else "") +
105- ("<div>" + html.escape(uploader) + "</div>" if uploader else "") +
106- ("<p>" + html.escape(description) + "</p>" if description else "") +
107- ("<p>Duration: " + html.escape(str(duration)) + "</p>" if duration else "") +
108- ("<p>View count: " + html.escape(str(view_count)) + "</p>" if view_count else "") +
109- ("<p>Tags: " + ", ".join([html.escape(str(tag)) for tag in tags]) + "</p>" if tags else "") +
110- ("<table>" +
111- "<thead>" +
112- "<tr><th>code</th><th>ext</th><th>size</th><th>format</th><th>resolution</th><th>audio</th><th>video</th></tr>" +
113- "</thead>\n<tbody>" +
114- "\n".join([renderFormat(form) + "\n" for form in formats]) +
115- "</tbody></table>\n" if len(thumbnails) > 0 else ""))
116-
117-def serveResult(conn, origUrl, res):
118- writeHeader(conn, origUrl, "text/html")
119- title = res.get('title')
120- webpage_url = res.get('webpage_url')
121- entries = res.get('entries', []) if res.get('_type', None) == 'playlist' else [res]
122- conn.send(("<!doctype html>" +
123- "<html>" +
124- "<head>" +
125- "<title>" + html.escape(title) + "</title></head>" +
126- "</head><body>\n" +
127- "<h1>" +
128- ("<a href='" + html.escape(webpage_url) + "'>" if webpage_url else "") +
129- html.escape(title) +
130- ("</a>" if webpage_url else "") +
131- "</h1>\n" +
132- "\n".join([renderEntry(entry) for entry in entries]) +
133- "<!--<pre>" + json.dumps(res, indent=True) + "</pre>-->\n" +
134- "</body></html>").encode('utf-8'))
135- conn.shutdown(socket.SHUT_WR)
136-
137-def serveOpen(conn, url):
138- uri = urllib.parse.urlparse(url)
139- if uri.path == "" or uri.path == "/": return serveHome(conn, url, uri)
140- if uri.scheme == "dpi": return serveEcho(conn, url, uri)
141- serveResource(conn, url, url[4:])
142-
143-def serveResource(conn, origUrl, url):
144- try:
145- res = ydl.extract_info(url, download=False)
146- except:
147- etype, value, tb = sys.exc_info()
148- writeHeader(conn, origUrl, "text/plain")
149- lines = traceback.format_exception(etype, value, tb)
150- conn.send((''.join(lines)).encode('utf-8'))
151- conn.shutdown(socket.SHUT_WR)
152- else:
153- serveResult(conn, origUrl, res)
154-
155-def handleConn(conn, addr):
156- buf = ""
157- while True:
158- data = conn.recv(1024)
159- if not data: break
160- buf += data.decode('utf-8')
161-
162- while len(buf) > 0:
163- if buf[0] != '<':
164- conn.close()
165- return
166-
167- m = authRe.match(buf)
168- if m:
169- buf = buf[m.end():]
170- continue
171-
172- m = openRe.match(buf)
173- if m:
174- buf = buf[m.end():]
175- url = m.group(1)
176- if url == "ydl:/quit": return serveQuit(conn, url)
177- threading.Thread(target=serveOpen, args=(conn, url)).start()
178- return
179-
180- m = byeRe.match(buf)
181- if m:
182- print("[ydl]: bye")
183- exit()
184-
185-stdin = socket.fromfd(sys.stdin.fileno(), socket.AF_INET, socket.SOCK_STREAM)
186-
187-ydl = youtube_dl.YoutubeDL({
188- 'no_color': True
189-})
190-
191-while True:
192- try:
193- conn, addr = stdin.accept()
194- except KeyboardInterrupt:
195- exit()
196- else:
197- handleConn(conn, addr)
ytdl.dpiView
@@ -1,0 +1,209 @@
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("[ytdl]: 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("[ytdl]: 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>ytdl</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 renderEntry(entry):
92 + thumbnails = entry.get('thumbnails', [])
93 + formats = entry.get('formats', [])
94 + uploader = entry.get('uploader')
95 + description = entry.get('description')
96 + duration = entry.get('duration')
97 + view_count = entry.get('view_count')
98 +
99 + if formats: del entry['formats']
100 + tags = entry.get('tags')
101 + return (
102 + ("<div>" +
103 + " ".join([renderThumbnail(thumb) + "\n" for thumb in thumbnails]) +
104 + "</div>\n" if len(thumbnails) > 0 else "") +
105 + ("<div>" + html.escape(uploader) + "</div>" if uploader else "") +
106 + ("<p>" + html.escape(description) + "</p>" if description else "") +
107 + ("<p>Duration: " + html.escape(str(duration)) + "</p>" if duration else "") +
108 + ("<p>View count: " + html.escape(str(view_count)) + "</p>" if view_count else "") +
109 + ("<p>Tags: " + ", ".join([html.escape(str(tag)) for tag in tags]) + "</p>" if tags else "") +
110 + ("<table>" +
111 + "<thead>" +
112 + "<tr><th>code</th><th>ext</th><th>size</th><th>format</th><th>resolution</th><th>audio</th><th>video</th></tr>" +
113 + "</thead>\n<tbody>" +
114 + "\n".join([renderFormat(form) + "\n" for form in formats]) +
115 + "</tbody></table>\n" if len(thumbnails) > 0 else ""))
116 +
117 +def serveResult(conn, origUrl, res):
118 + writeHeader(conn, origUrl, "text/html")
119 + title = res.get('title')
120 + webpage_url = res.get('webpage_url')
121 + entries = res.get('entries', []) if res.get('_type', None) == 'playlist' else [res]
122 + conn.send(("<!doctype html>" +
123 + "<html>" +
124 + "<head>" +
125 + "<title>" + html.escape(title) + "</title></head>" +
126 + "</head><body>\n" +
127 + "<h1>" +
128 + ("<a href='" + html.escape(webpage_url) + "'>" if webpage_url else "") +
129 + html.escape(title) +
130 + ("</a>" if webpage_url else "") +
131 + "</h1>\n" +
132 + "\n".join([renderEntry(entry) for entry in entries]) +
133 + "<!--<pre>" + json.dumps(res, indent=True) + "</pre>-->\n" +
134 + "</body></html>").encode('utf-8'))
135 + conn.shutdown(socket.SHUT_WR)
136 +
137 +def serveOpen(conn, url):
138 + uri = urllib.parse.urlparse(url)
139 + if uri.scheme == "dpi": return serveEcho(conn, url, uri)
140 + if uri.netloc == "" and (uri.path == "" or uri.path == "/"):
141 + return serveHome(conn, url, uri)
142 + if uri.scheme == "ytdl" or uri.scheme == "ydl":
143 + if '.' in uri.netloc:
144 + return serveResource(conn, url, urllib.parse.urlunparse((
145 + 'https',
146 + uri.netloc,
147 + uri.path,
148 + uri.params,
149 + uri.query,
150 + uri.fragment
151 + )))
152 + return serveResource(conn, url, re.sub('yt?dl:(//)?', '', url))
153 + return serveEcho(conn, url, uri)
154 +
155 +def serveResource(conn, origUrl, url):
156 + try:
157 + res = ytdl.extract_info(url, download=False)
158 + except:
159 + etype, value, tb = sys.exc_info()
160 + writeHeader(conn, origUrl, "text/plain")
161 + lines = traceback.format_exception(etype, value, tb)
162 + conn.send((''.join(lines)).encode('utf-8'))
163 + conn.shutdown(socket.SHUT_WR)
164 + else:
165 + serveResult(conn, origUrl, res)
166 +
167 +def handleConn(conn, addr):
168 + buf = ""
169 + while True:
170 + data = conn.recv(1024)
171 + if not data: break
172 + buf += data.decode('utf-8')
173 +
174 + while len(buf) > 0:
175 + if buf[0] != '<':
176 + conn.close()
177 + return
178 +
179 + m = authRe.match(buf)
180 + if m:
181 + buf = buf[m.end():]
182 + continue
183 +
184 + m = openRe.match(buf)
185 + if m:
186 + buf = buf[m.end():]
187 + url = m.group(1)
188 + if url == "ytdl:/quit": return serveQuit(conn, url)
189 + threading.Thread(target=serveOpen, args=(conn, url)).start()
190 + return
191 +
192 + m = byeRe.match(buf)
193 + if m:
194 + print("[ytdl]: bye")
195 + exit()
196 +
197 +stdin = socket.fromfd(sys.stdin.fileno(), socket.AF_INET, socket.SOCK_STREAM)
198 +
199 +ytdl = youtube_dl.YoutubeDL({
200 + 'no_color': True
201 +})
202 +
203 +while True:
204 + try:
205 + conn, addr = stdin.accept()
206 + except KeyboardInterrupt:
207 + exit()
208 + else:
209 + handleConn(conn, addr)

Built with git-ssb-web