shithub: nvi

ref: d81d2b35559fea6684cee44a5d4f494fc585e38d
dir: nvi/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 = jfield(f, JSONString, "url", nil)) == 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 = jstrdup(f, "qualityLabel");
		if(strstr(s, ", ") != nil) /* I know, not the best way */
			fmt->included |= Iaudio;
	}

	fmt->id = jint(f, "itag");
	fmt->sz = jint(f, "clen");

	return 0;
}

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

	j = nil;
	i = 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");
		goto err;
	}

	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 = jstrdup(z, "author");
			i->title = jstrdup(z, "title");
			i->description = jstrdup(z, "description");
			i->duration = jint(z, "lengthSeconds");
			tmtime(&i->published, jint(z, "published"), nil);

			for(fmtname = fmtnames; *fmtname; fmtname++){
				if((x = jsonbyname(z, *fmtname)) == nil)
					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;
err:
	werrstr("youtube: %r");
	free(i);
	jsonfree(j);
	return nil;
}