shithub: nvi

Download patch

ref: 485a82a087b140233f79c0a1211686f5f9cc49f1
parent: 8d8227ad5b80f5116e917a9adbd1a873f037041e
author: Sigrid Solveig Haflínudóttir <ftrvxmtrx@gmail.com>
date: Tue Jun 22 16:24:58 EDT 2021

add peertube support and fix description/etc

--- a/README.md
+++ b/README.md
@@ -1,6 +1,7 @@
 # nvi
 
-Downloads a Youtube video using Invidious public servers.
+Downloads a PeerTube video, or a Youtube video (using Invidious public
+servers).
 
 Multiple `-A` and `-V` can be used to prioritize specific stream
 formats.  The values for these options can be IDs and quality, ie `18`
--- a/mkfile
+++ b/mkfile
@@ -8,6 +8,7 @@
 OFILES=\
 	nvi.$O\
 	util.$O\
+	peertube.$O\
 	youtube.$O\
 
 </sys/src/cmd/mkone
--- a/nvi.c
+++ b/nvi.c
@@ -76,27 +76,29 @@
 
 	if(argc != 1)
 		usage();
-	if((vid = strrchr(argv[0], '/')) != nil)
+	if(strstr(argv[0], "youtu") != nil && (vid = strrchr(argv[0], '/')) != nil){
 		vid++;
-	else
+	}else{
 		vid = argv[0];
-
-	if((info = fun(vid)) == nil){
-		fprint(2, "%r\n");
-		threadexitsall("failed");
+		fun = peertube;
 	}
 
+	if((info = fun(vid)) == nil)
+		sysfatal("%r");
+
 	qsort(info->fmt, info->nfmt, sizeof(Format), cmpfmt);
 	if(cmd == Cinfo){
 		print("ID\tQUALITY\tSIZE\tFORMAT\n");
 		for(i = 0, f = info->fmt; i < info->nfmt; i++, f++)
-			print("%d\t%s\t%Z\t%s\n", f->id, f->quality, f->sz, f->type);
+			print("%d\t%s\t%Z\t%s\n", f->id, f->quality ? f->quality : "----", f->sz, f->type);
 		print("\n");
 		print("author: %s\n", info->author);
 		print("title: %s\n", info->title);
-		print("description: %s\n", info->title);
-		print("duration: %P\n", info->duration);
-		print("published: %τ\n", tmfmt(tmtime(&tm, info->published, nil), "YYYY/MM/DD"));
+		print("description: %s\n", info->description);
+		if(info->duration != 0)
+			print("duration: %P\n", info->duration);
+		if(info->published != 0)
+			print("published: %τ\n", tmfmt(tmtime(&tm, info->published, nil), "YYYY/MM/DD"));
 	}else if(cmd == Cdownload){
 		for(j = 0, fa = nil, f = info->fmt; j < info->nfmt && fa == nil; j++, f++){
 			if((f->included == Iaudio) == 0)
--- a/nvi.h
+++ b/nvi.h
@@ -6,7 +6,6 @@
 	char *type;
 	char *quality; /* "unknown" for audio, "360p"/etc for video */
 	vlong sz;
-	vlong bitrate;
 	int included; /* Iaudio|Ivideo */
 	int fps;
 	int id;
@@ -33,6 +32,7 @@
 extern int cmd;
 extern int debug;
 
+Info *peertube(char *url);
 Info *youtube(char *vid);
 
 int pipeexec(int *fd, char *file, char **argv);
--- /dev/null
+++ b/peertube.c
@@ -1,0 +1,148 @@
+#include <u.h>
+#include <libc.h>
+#include <json.h>
+#include "nvi.h"
+
+static int
+addfmt(Info *i, JSON *f)
+{
+	JSON *x, *z, *j;
+	Format *fmt;
+	char *s, *t;
+	JSONEl *e;
+	int fd;
+
+	if((x = jsonbyname(f, "metadataUrl")) == nil){
+		werrstr("no url");
+		return -1;
+	}
+	j = nil;
+	if((fd = hget(jsonstr(x), -1)) >= 0){
+		if((s = readall(fd)) != nil){
+			j = jsonparse(s);
+			free(s);
+		}
+		close(fd);
+	}
+	procwait();
+	if(j == nil){
+		werrstr("peertube: %r");
+		return -1;
+	}
+
+	if((x = jsonbyname(f, "fileDownloadUrl")) == nil){
+		werrstr("no url");
+		jsonfree(j);
+		return -1;
+	}
+	i->nfmt++;
+	if((i->fmt = realloc(i->fmt, i->nfmt * sizeof(*fmt))) == nil)
+		sysfatal("memory");
+	fmt = &i->fmt[i->nfmt - 1];
+	memset(fmt, 0, sizeof(*fmt));
+	fmt->url = estrdup(jsonstr(x));
+	if((x = jsonbyname(f, "size")) != nil)
+		fmt->sz = x->n;
+
+	s = strdup("");
+	if((x = jsonbyname(j, "streams")) != nil && x->t == JSONArray){
+		for(e = x->first; e != nil; e = e->next){
+			if((x = jsonbyname(e->val, "codec_name")) != nil){
+				t = smprint("%s%s%s", s, *s ? "," : "", jsonstr(x));
+				free(s);
+				s = t;
+			}
+			if((x = jsonbyname(e->val, "codec_type")) != nil){
+				t = jsonstr(x);
+				if(strcmp(t, "video") == 0)
+					fmt->included |= Ivideo;
+				else if(strcmp(t, "audio") == 0)
+					fmt->included |= Iaudio;
+			}
+		}
+	}
+	fmt->type = s;
+	jsonfree(j);
+
+	if((x = jsonbyname(f, "resolution")) != nil){
+		if((z = jsonbyname(x, "id")) != nil)
+			fmt->id = z->n;
+		if((fmt->included & Ivideo) && (z = jsonbyname(x, "label")) != nil)
+			fmt->quality = estrdup(jsonstr(z));
+		if((z = jsonbyname(f, "fps")) != nil)
+			fmt->fps = z->n;
+	}
+
+	return 0;
+}
+
+Info *
+peertube(char *url)
+{
+	int fd, peertube;
+	char *s, *o, *id;
+	JSON *j, *z;
+	JSONEl *e, *f;
+	Info *i;
+
+	peertube = 0;
+	if((fd = hget(url, -1)) >= 0){
+		if((s = readall(fd)) != nil){
+			if((o = strstr(s, "property=\"og:platform\"")) != nil && strstr(o+23, "content=\"PeerTube\"") != nil)
+				peertube = 1;
+			free(s);
+		}
+		close(fd);
+	}
+	procwait();
+	if(!peertube){
+		if(fd >= 0)
+			werrstr("not peertube");
+		return nil;
+	}
+
+	if((id = strrchr(url, '/')) == nil || (s = strstr(url, "://")) == nil || (s = strchr(s+3, '/')) == nil){
+		werrstr("bad url");
+		return nil;
+	}
+	url = smprint("%.*s/api/v1/videos%s", (int)(s-url), url, id);
+	fd = hget(url, -1);
+	free(url);
+
+	j = nil;
+	if(fd >= 0){
+		if((s = readall(fd)) != nil){
+			j = jsonparse(s);
+			free(s);
+		}
+		close(fd);
+	}
+	procwait();
+	if(j == nil){
+		werrstr("peertube: %r");
+		return nil;
+	}
+
+	if((i = calloc(1, sizeof(*i))) == nil)
+		sysfatal("memory");
+
+	if((z = jsonbyname(j, "account")) != nil)
+		i->author = estrdup(jsonstr(jsonbyname(z, "displayName")));
+	i->title = estrdup(jsonstr(jsonbyname(j, "name")));
+	i->description = estrdup(jsonstr(jsonbyname(j, "description")));
+	i->duration = jsonbyname(j, "duration")->n;
+	// FIXME i->published = jsonstr(jsonbyname(z, "published"));
+
+	if((z = jsonbyname(j, "streamingPlaylists")) != nil && z->t == JSONArray){
+		for(e = z->first; e != nil; e = e->next){
+			if((z = jsonbyname(e->val, "files")) != nil && z->t == JSONArray){
+				for(f = z->first; f != nil; f = f->next)
+					addfmt(i, f->val);
+			}
+		}
+	}
+
+	jsonfree(j);
+
+	return i;
+}
--- a/youtube.c
+++ b/youtube.c
@@ -7,17 +7,17 @@
 static char *fmtnames[] = {"adaptiveFormats", "formatStreams", nil};
 
 static int
-addfmt(Info *i, JSONEl *f)
+addfmt(Info *i, JSON *f)
 {
 	Format *fmt;
 	JSON *x;
 	char *s;
 
-	if((x = jsonbyname(f->val, "url")) == nil){
+	if((x = jsonbyname(f, "url")) == nil){
 		werrstr("no url");
 		return -1;
 	}
-	if((s = jsonstr(jsonbyname(f->val, "type"))) == nil){
+	if((s = jsonstr(jsonbyname(f, "type"))) == nil){
 		werrstr("no type");
 		return -1;
 	}
@@ -31,22 +31,19 @@
 	fmt->type = estrdup(s);
 	if(strncmp(s, "audio/", 6) == 0){
 		fmt->included |= Iaudio;
-		fmt->quality = estrdup("----");
 	}else if(strncmp(s, "video/", 6) == 0){
 		fmt->included |= Ivideo;
-		fmt->quality = estrdup(jsonstr(jsonbyname(f->val, "qualityLabel")));
-		if((x = jsonbyname(f->val, "fps")) != nil)
+		fmt->quality = estrdup(jsonstr(jsonbyname(f, "qualityLabel")));
+		if((x = jsonbyname(f, "fps")) != nil)
 			fmt->fps = x->n;
 		if(strstr(s, ", ") != nil) /* I know, not the best way */
 			fmt->included |= Iaudio;
 	}
 
-	if((x = jsonbyname(f->val, "itag")) != nil)
+	if((x = jsonbyname(f, "itag")) != nil)
 		fmt->id = atoi(jsonstr(x));
-	if((x = jsonbyname(f->val, "clen")) != nil)
+	if((x = jsonbyname(f, "clen")) != nil)
 		fmt->sz = atoll(jsonstr(x));
-	if((x = jsonbyname(f->val, "bitrate")) != nil)
-		fmt->bitrate = atoll(jsonstr(x));
 
 	return 0;
 }
@@ -113,7 +110,7 @@
 				}
 
 				for(f = x->first; f != nil; f = f->next)
-					addfmt(i, f);
+					addfmt(i, f->val);
 			}
 
 			if(i->nfmt < 1){