ref: efd5647c5a30f87becd0d96e9f209b70b41ab0cd
dir: /mkplist.c/
#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, *s;
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);
if(curr->title == nil){
path[len] = 0;
if((s = utfrrune(path, '/')) == nil)
s = &path[len];
path[len] = '/';
curr->title = strdup(s+1);
}
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);
}