shithub: nvi

ref: 485a82a087b140233f79c0a1211686f5f9cc49f1
dir: /youtube.c/

View raw version
#include <u.h>
#include <libc.h>
#include <json.h>
#include "nvi.h"

static char *instlst = "https://api.invidious.io/instances.json?sort_by=health,type,users,signup";
static char *fmtnames[] = {"adaptiveFormats", "formatStreams", nil};

static int
addfmt(Info *i, JSON *f)
{
	Format *fmt;
	JSON *x;
	char *s;

	if((x = jsonbyname(f, "url")) == nil){
		werrstr("no url");
		return -1;
	}
	if((s = jsonstr(jsonbyname(f, "type"))) == nil){
		werrstr("no type");
		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));

	fmt->type = estrdup(s);
	if(strncmp(s, "audio/", 6) == 0){
		fmt->included |= Iaudio;
	}else if(strncmp(s, "video/", 6) == 0){
		fmt->included |= Ivideo;
		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, "itag")) != nil)
		fmt->id = atoi(jsonstr(x));
	if((x = jsonbyname(f, "clen")) != nil)
		fmt->sz = atoll(jsonstr(x));

	return 0;
}

Info *
youtube(char *vid)
{
	JSON *j, *z, *x;
	JSONEl *e, *f;
	char *s, *u, **fmtname;
	Info *i;
	int fd;

	j = nil;
	if((fd = hget(instlst, -1)) >= 0){
		if((s = readall(fd)) != nil){
			j = jsonparse(s);
			free(s);
		}
		close(fd);
	}
	procwait();
	if(j == nil){
		werrstr("instances: %r");
		return nil;
	}

	for(e = j->first, i = nil; e != nil && i == nil; e = e->next){
		if(e->val->t != JSONArray || e->val->first == nil || e->val->first->next == nil)
			continue;
		f = e->val->first;
		if(f->val->t != JSONString) /* first is the url */
			continue;
		if(f->next->val->t != JSONObject) /* second is the attributes */
			continue;
		if((s = jsonstr(jsonbyname(f->next->val, "type"))) == nil)
			continue;
		if(strncmp(s, "http", 4) != 0) /* don't even try onion */
			continue;

		u = smprint("%s://%s/api/v1/videos/%s", s, jsonstr(e->val->first->val), vid);
		s = nil;
		z = nil;
		werrstr("");
		if((fd = hget(u, -1)) < 0 || (s = readall(fd)) == nil || (z = jsonparse(s)) == nil || z->t != JSONObject){
			free(s);
			werrstr("%s: %r", u);
		}else{
			free(s);

			if((i = calloc(1, sizeof(*i))) == nil)
				sysfatal("memory");

			i->author = estrdup(jsonstr(jsonbyname(z, "author")));
			i->title = estrdup(jsonstr(jsonbyname(z, "title")));
			i->description = estrdup(jsonstr(jsonbyname(z, "description")));
			i->duration = jsonbyname(z, "lengthSeconds")->n;
			i->published = jsonbyname(z, "published")->n;

			for(fmtname = fmtnames; *fmtname; fmtname++){
				if((x = jsonbyname(z, *fmtname)) == nil){
					if(debug) fprint(2, "%s: no streams\n", u);
					continue;
				}

				for(f = x->first; f != nil; f = f->next)
					addfmt(i, f->val);
			}

			if(i->nfmt < 1){
				free(i->title);
				free(i->description);
				free(i);
				i = nil;
			}
		}
		close(fd);
		jsonfree(z);
		free(u);

		if(fd >= 0)
			procwait();
	}

	jsonfree(j);

	return i;
}