ref: 4c5bf2f861bb45cb1c2fbce54217c06948c1c8ab
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 */ }; 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), "%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\n", Pimage, m->imageoffset, m->imagesize, m->imagereader); 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; 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); } 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); }