shithub: zuke

Download patch

ref: aaaaadd1005ce272f9257c2e3987653afefbafbb
parent: 5d2537db828012ebd648101443a24cee3cbd7158
author: Sigrid Solveig Haflínudóttir <ftrvxmtrx@gmail.com>
date: Sat Feb 20 11:05:12 EST 2016

add a tool to create playlists

--- a/mkfile
+++ b/mkfile
@@ -2,6 +2,7 @@
 MAN=/sys/man/1
 
 TARG=\
+	mkplist\
 	zuke\
 
 BIN=/$objtype/bin/audio
--- /dev/null
+++ b/mkplist.c
@@ -1,0 +1,298 @@
+#include <u.h>
+#include <libc.h>
+#include <tags.h>
+
+enum
+{
+	Maxartist = 16, /* max artists for a track */
+	Maxname = 256+2, /* seems enough? */
+	Maxdepth = 16, /* max recursion depth */
+};
+
+typedef struct Meta Meta;
+
+struct Meta
+{
+	char *artist[Maxartist];
+	char *album;
+	char *title;
+	char *date;
+	char *track;
+	char *path;
+	int numartist;
+	int duration;
+	int samplerate;
+	int channels;
+	int imageoffset;
+	int imagesize;
+	int imagereader; /* non-zero if a special reader required */
+};
+
+static int fd;
+static Meta *curr;
+static Meta *all;
+static int numall;
+
+static void
+freemeta(Meta *m)
+{
+	int i;
+
+	if(m != nil){
+		for(i = 0; i < m->numartist; i++)
+			free(m->artist[i]);
+		free(m->album);
+		free(m->title);
+		free(m->date);
+		free(m->track);
+		free(m->path);
+		numall--;
+	}
+}
+
+static Meta *
+newmeta(void)
+{
+	if(all == nil)
+		all = mallocz(sizeof(Meta), 1);
+	else if((numall & (numall-1)) == 0)
+		all = realloc(all, numall*2*sizeof(Meta));
+
+	if(all == nil)
+		return nil;
+
+	memset(&all[numall++], 0, sizeof(Meta));
+	return &all[numall-1];
+}
+
+static void
+cb(Tagctx *ctx, int t, const char *v, int offset, int size, Tagread f)
+{
+	int i;
+
+	USED(ctx);
+
+	switch(t){
+	case Tartist:
+		if(curr->numartist < Maxartist){
+			for(i = 0; i < curr->numartist; i++){
+				if(cistrcmp(curr->artist[i], v) == 0)
+					return;
+			}
+			curr->artist[curr->numartist++] = strdup(v);
+		}
+		break;
+	case Talbum:
+		if(curr->album == nil)
+			curr->album = strdup(v);
+		break;
+	case Ttitle:
+		if(curr->title == nil)
+			curr->title = strdup(v);
+		break;
+	case Tdate:
+		if(curr->date == nil)
+			curr->date = strdup(v);
+		break;
+	case Ttrack:
+		if(curr->track == nil)
+			curr->track = strdup(v);
+		break;
+	case Timage:
+		curr->imageoffset = offset;
+		curr->imagesize = size;
+		curr->imagereader = f != nil;
+		break;
+	}
+}
+
+static int
+ctxread(Tagctx *ctx, void *buf, int cnt)
+{
+	USED(ctx);
+	return read(fd, buf, cnt);
+}
+
+static int
+ctxseek(Tagctx *ctx, int offset, int whence)
+{
+	USED(ctx);
+	return seek(fd, offset, whence);
+}
+
+static char buf[1024];
+static Tagctx ctx =
+{
+	.read = ctxread,
+	.seek = ctxseek,
+	.tag = cb,
+	.buf = buf,
+	.bufsz = sizeof(buf),
+	.aux = nil,
+};
+
+static int
+scan(char **dir, int depth)
+{
+	char *path;
+	Dir *buf, *d;
+	long n;
+	int dirfd, len, flen;
+
+	if((dirfd = open(*dir, OREAD)) < 0)
+		sysfatal("%s: %r", *dir);
+	len = strlen(*dir);
+	if((*dir = realloc(*dir, len+1+Maxname)) == nil)
+		sysfatal("no memory");
+	path = *dir;
+	path[len] = '/';
+
+	for(;;){
+		if((n = dirread(dirfd, &buf)) < 0)
+			sysfatal("dirread: %r");
+		if(n == 0){
+			free(buf);
+			break;
+		}
+
+		for(d = buf; n > 0; n--, d++){
+			if(strcmp(d->name, ".") == 0 || strcmp(d->name, "..") == 0)
+				continue;
+
+			if((d->mode & DMDIR) == 0){ /* if it's a regular file, apply filter */
+				if((flen = strlen(d->name)) < 5)
+					continue;
+				if(strcmp(&d->name[flen-4], ".mp3") != 0 &&
+				   strcmp(&d->name[flen-4], ".ogg") != 0 &&
+				   strcmp(&d->name[flen-5], ".flac") != 0){
+					continue;
+				}
+			}
+
+			path[len+1+Maxname-2] = 0;
+			strncpy(&path[len+1], d->name, Maxname);
+			if(path[len+1+Maxname-2] != 0)
+				sysfatal("Maxname=%d was a bad choice", Maxname);
+
+			if((d->mode & DMDIR) == 0){
+				ctx.filename = path;
+				if((fd = open(path, OREAD)) < 0)
+					fprint(2, "%s: %r\n", path);
+				else if((curr = newmeta()) == nil)
+					sysfatal("no memory");
+				else if(tagsget(&ctx) != 0){
+					fprint(2, "%s: no tags\n", path);
+					freemeta(curr);
+				}else{
+					curr->path = strdup(path);
+					curr->duration = ctx.duration;
+					curr->samplerate = ctx.samplerate;
+					curr->channels = ctx.channels;
+				}
+				close(fd);
+			}else if(depth < Maxdepth){ /* recurse into the directory */
+				scan(dir, depth+1);
+				path = *dir;
+			}else{
+				fprint(2, "%s: too deep\n", path);
+			}
+		}
+		free(buf);
+	}
+
+	close(dirfd);
+
+	return 0;
+}
+
+static void
+printmeta(Meta *m, int index)
+{
+	static char buf[4096];
+	char *s;
+	int i;
+
+	buf[sizeof(buf)-2] = 0;
+	s = buf;
+
+	for(i = 0; i < m->numartist; i++)
+		s = seprint(s, buf+sizeof(buf), "artist %ld %s\n", strlen(m->artist[i]), m->artist[i]);
+	if(m->album != nil)
+		s = seprint(s, buf+sizeof(buf), "album %ld %s\n", strlen(m->album), m->album);
+	if(m->title != nil)
+		s = seprint(s, buf+sizeof(buf), "title %ld %s\n", strlen(m->title), m->title);
+	if(m->date != nil)
+		s = seprint(s, buf+sizeof(buf), "date %ld %s\n", strlen(m->date), m->date);
+	if(m->track != nil)
+		s = seprint(s, buf+sizeof(buf), "track %ld %s\n", strlen(m->track), m->track);
+	if(m->duration > 0)
+		s = seprint(s, buf+sizeof(buf), "duration %d\n", m->duration);
+	if(m->samplerate > 0)
+		s = seprint(s, buf+sizeof(buf), "samplerate %d\n", m->samplerate);
+	if(m->channels > 0)
+		s = seprint(s, buf+sizeof(buf), "channels %d\n", m->channels);
+	if(m->imagesize > 0)
+		s = seprint(s, buf+sizeof(buf), "image %d %d %d\n", m->imageoffset, m->imagesize, m->imagereader);
+	s = seprint(s, buf+sizeof(buf), "path %s\n", m->path);
+
+	if(buf[sizeof(buf)-2] != 0)
+		sysfatal("buffer size of %d bytes was a bad choice", sizeof(buf));
+
+	print("# %d %d\n%s", index, (int)(s-buf), buf);
+}
+
+static int
+cmpmeta(void *a_, void *b_)
+{
+	Meta *a, *b;
+	int i, x;
+
+	a = a_;
+	b = b_;
+
+	for(i = 0; i < a->numartist && i < b->numartist; i++){
+		if((x = cistrcmp(a->artist[i], b->artist[i])) != 0)
+			return x;
+	}
+
+	if(a->date != nil || b->date != nil){
+		if(a->date == nil && b->date != nil) return -1;
+		if(a->date != nil && b->date == nil) return 1;
+		if((x = atoi(a->date) - atoi(b->date)) != 0) return x;
+	}else if(a->album != nil || b->album != nil){
+		if(a->album == nil && b->album != nil) return -1;
+		if(a->album != nil && b->album == nil) return 1;
+		if((x = cistrcmp(a->album, b->album)) != 0) return x;
+	}
+
+	if(a->track != nil || b->track != nil){
+		if(a->track == nil && b->track != nil) return -1;
+		if(a->track != nil && b->track == nil) return 1;
+		if((x = atoi(a->track) - atoi(b->track)) != 0) return x;
+	}
+
+	return cistrcmp(a->path, b->path);
+}
+
+void
+main(int argc, char **argv)
+{
+	char *dir;
+	int i;
+
+	if(argc < 2){
+		fprint(2, "usage: mkplist DIR [DIR2 ...] > noise.plist\n");
+		exits("usage");
+	}
+
+	for(i = 1; i < argc; i++){
+		dir = strdup(argv[i]);
+		scan(&dir, 0);
+	}
+	if(numall > 0){
+		qsort(all, numall, sizeof(Meta), cmpmeta);
+		for(i = 0; i < numall; i++)
+			printmeta(&all[i], i);
+	}
+	fprint(2, "found %d tagged tracks\n", numall);
+}