ref: 087feb1e1d07b325d126ce315b9b4eba9cea9e59
dir: /mkplist.c/
#include <u.h> #include <libc.h> #include <bio.h> #include <tags.h> #include "plist.h" #include "icy.c" enum { Maxname = 256+2, /* seems enough? */ Maxdepth = 16, /* max recursion depth */ }; #define MAX(a, b) (a > b ? a : b) static Biobuf *bf; static Meta *curr; static Meta *all; static int numall; static int firstiscomposer; static int keepfirstartist; static char *fmts[] = { [Fmp3] = "mp3", [Fogg] = "ogg", [Fflac] = "flac", [Fm4a] = "m4a", [Fopus] = "opus", }; static Meta * newmeta(void) { if(numall == 0){ free(all); all = nil; } 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 *k, const char *v, int offset, int size, Tagread f) { int i, iscomposer; USED(ctx); switch(t){ case Tartist: if(curr->numartist < Maxartist){ iscomposer = strcmp(k, "TCM") == 0 || strcmp(k, "TCOM") == 0; /* prefer lead performer/soloist, helps when TP2/TPE2 is the first one and is set to "VA" */ /* always put composer first, if available */ if(iscomposer || (!keepfirstartist && (strcmp(k, "TP1") == 0 || strcmp(k, "TPE1") == 0))) { if(curr->numartist > 0) curr->artist[curr->numartist] = curr->artist[curr->numartist-1]; curr->artist[0] = strdup(v); curr->numartist++; keepfirstartist = 1; firstiscomposer = iscomposer; return; } 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 Bread(bf, buf, cnt); } static int ctxseek(Tagctx *ctx, int offset, int whence) { USED(ctx); return Bseek(bf, offset, whence); } static char buf[4096]; static Tagctx ctx = { .read = ctxread, .seek = ctxseek, .tag = cb, .buf = buf, .bufsz = sizeof(buf), .aux = nil, }; static void scanfile(char *path) { int res; char *s; if((bf = Bopen(path, OREAD)) == nil){ fprint(2, "%s: %r\n", path); return; } if((curr = newmeta()) == nil) sysfatal("no memory"); firstiscomposer = keepfirstartist = 0; res = tagsget(&ctx); if(ctx.format != Funknown){ if(res != 0) fprint(2, "%s: no tags\n", path); }else{ numall--; Bterm(bf); return; } if(ctx.duration == 0) fprint(2, "%s: no duration\n", path); if(curr->title == nil){ if((s = utfrrune(path, '/')) == nil) s = path; curr->title = strdup(s+1); } curr->path = strdup(path); curr->duration = ctx.duration; if(ctx.format >= nelem(fmts)) sysfatal("mkplist needs a rebuild with updated libtags"); curr->filefmt = fmts[ctx.format]; Bterm(bf); } static int scan(char **dir, int depth) { char *path; Dir *buf, *d; long n; int dirfd, len; 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(n = 0, buf = nil; n >= 0;){ if((n = dirread(dirfd, &buf)) < 0){ path[len] = 0; scanfile(path); break; } if(n == 0){ free(buf); break; } for(d = buf; n > 0; n--, d++){ if(strcmp(d->name, ".") == 0 || strcmp(d->name, "..") == 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){ scanfile(path); }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->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%c %s\n", Pfilefmt, m->filefmt, 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_; ae = utfrrune(a->path, '/'); be = utfrrune(b->path, '/'); if(ae != nil && be != nil && (x = cistrncmp(a->path, b->path, MAX(ae-a->path, be-b->path))) != 0) /* different path */ return x; /* same path, must be the same album/cd, but first check */ for(i = 0; i < a->numartist && i < b->numartist; i++){ if((x = cistrcmp(a->artist[i], b->artist[i])) != 0){ if(a->album != nil && b->album != nil && cistrcmp(a->album, b->album) != 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++){ if(strncmp(argv[i], "http://", 7) == 0 || strncmp(argv[i], "https://", 8) == 0){ if((curr = newmeta()) == nil) sysfatal("no memory"); curr->title = argv[i]; curr->path = argv[i]; curr->filefmt = ""; if(icyfill(curr) != 0) fprint(2, "%s: %r\n", argv[i]); }else{ 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); exits(nil); }