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);
+}