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){