shithub: zuke

ref: b1dc12bf2d11aa0ab8becc35d185cd586bb4b279
dir: /mkplist.c/

View raw version
#include <u.h>
#include <libc.h>
#include <tags.h>
#include "plist.h"

enum
{
	Maxname = 256+2, /* seems enough? */
	Maxdepth = 16, /* max recursion depth */
};

#define MAX(a, b) (a > b ? a : b)

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:
		if(curr->imagefmt == nil){
			curr->imagefmt = strdup(v);
			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), "%c%ld %s\n", Partist, strlen(m->artist[i]), m->artist[i]);
	if(m->album != nil)
		s = seprint(s, buf+sizeof(buf), "%c%ld %s\n", Palbum, strlen(m->album), m->album);
	if(m->title != nil)
		s = seprint(s, buf+sizeof(buf), "%c%ld %s\n", Ptitle, strlen(m->title), m->title);
	if(m->date != nil)
		s = seprint(s, buf+sizeof(buf), "%c%ld %s\n", Pdate, strlen(m->date), m->date);
	if(m->track != nil)
		s = seprint(s, buf+sizeof(buf), "%c%ld %s\n", Ptrack, strlen(m->track), m->track);
	if(m->duration > 0)
		s = seprint(s, buf+sizeof(buf), "%c %d\n", Pduration, m->duration);
	if(m->samplerate > 0)
		s = seprint(s, buf+sizeof(buf), "%c %d\n", Psamplerate, m->samplerate);
	if(m->channels > 0)
		s = seprint(s, buf+sizeof(buf), "%c %d\n", Pchannels, m->channels);
	if(m->imagesize > 0)
		s = seprint(s, buf+sizeof(buf), "%c %d %d %d %s\n", Pimage, m->imageoffset, m->imagesize, m->imagereader, m->imagefmt);
	s = seprint(s, buf+sizeof(buf), "%c %s\n", Ppath, m->path);

	if(buf[sizeof(buf)-2] != 0)
		sysfatal("buffer size of %d bytes was a bad choice", sizeof(buf));

	print("%c %d %d\n%s", Precord, index, (int)(s-buf), buf);
}

static int
cmpmeta(void *a_, void *b_)
{
	Meta *a, *b;
	char *ae, *be;
	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;
	}

	ae = utfrrune(a->path, '/');
	be = utfrrune(b->path, '/');
	if(ae != nil && be != nil && (x = strncmp(a->path, b->path, MAX(ae-a->path, be-b->path))) != 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);
	}
	qsort(all, numall, sizeof(Meta), cmpmeta);
	print("# %d\n", numall);
	for(i = 0; i < numall; i++){
		if(all[i].numartist < 1)
			fprint(2, "no artists: %s\n", all[i].path);
		if(all[i].title == nil)
			fprint(2, "no title: %s\n", all[i].path);
		printmeta(&all[i], i);
	}
	fprint(2, "found %d tagged tracks\n", numall);
}