ref: a1aa7038ba62a46b1173b609e4c884b1919d3949
author: Jacob Moody <jsmoody@iastate.edu>
date: Sun Sep 8 17:02:52 EDT 2019
Initial commit
--- /dev/null
+++ b/LICENSE
@@ -1,0 +1,9 @@
+Copyright 2019 Jacob Moody
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--- /dev/null
+++ b/dat.h
@@ -1,0 +1,55 @@
+enum decmsg{
+ START,
+ STOP,
+ PAUSE,
+};
+
+/*
+* Dec represents a decoder process.
+* ctl is the control channel for pausing, starting, and stoping the proc.
+*/
+typedef struct Dec Dec;
+struct Dec{
+ int decpid;
+ int ctlpid;
+ Channel *ctl;
+};
+
+/*
+* ID3v1 represents the first version of ID3 metainformation.
+* The spec does not define character set, so we treat it as
+* UTF8, which should cover most bases.
+* See: http://id3.org/ID3v1
+*/
+typedef struct ID3v1 ID3v1;
+struct ID3v1{
+ Rune *title;
+ Rune *artist;
+ Rune *album;
+ int year;
+ Rune *comment;
+ char genre;
+};
+
+typedef struct VorbisMeta VorbisMeta;
+struct VorbisMeta{
+ uint ncom;
+ Rune **key;
+ Rune **val;
+};
+
+typedef struct FlacPic FlacPic;
+struct FlacPic{
+ char *mime;
+ Rune *desc;
+ Point p;
+ uvlong size;
+ uchar *data;
+ Image *i;
+};
+
+typedef struct FlacMeta FlacMeta;
+struct FlacMeta{
+ VorbisMeta *com;
+ FlacPic *pic;
+};
\ No newline at end of file
--- /dev/null
+++ b/dec.c
@@ -1,0 +1,101 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+
+#include "dat.h"
+#include "fncs.h"
+
+/* Proc argument structs */
+typedef struct {
+ int outpipe;
+ char *file;
+ Channel *cpid;
+} Decodearg;
+
+typedef struct {
+ int inpipe;
+ Channel *ctl;
+} Ctlarg;
+
+void
+decodeproc(void *arg)
+{
+ Decodearg *a = arg;
+ int nfd;
+
+ nfd = open("/dev/null", OREAD|OWRITE);
+ dup(nfd, 0);
+ dup(nfd, 2);
+ dup(a->outpipe, 1);
+ procexecl(a->cpid, "/bin/play", "play", "-o", "/fd/1", a->file, nil);
+}
+
+void
+ctlproc(void *arg)
+{
+ int afd;
+ int bufsize;
+ int n;
+ enum decmsg msg;
+ char *buf;
+
+ Ctlarg *a = arg;
+ Channel *c = a->ctl;
+ int inpipe = a->inpipe;
+ free(a);
+
+ afd = open("/dev/audio", OWRITE);
+ if(afd < 0)
+ threadexits("could not open audio device");
+ bufsize = iounit(afd);
+ buf = emalloc(bufsize);
+
+ for(;;){
+ if(nbrecv(c, &msg) != 0)
+ switch(msg){
+ case STOP:
+ threadexits("Stopped");
+ break;
+ case PAUSE:
+ //Block until we get a START message
+ while(msg != START)
+ recv(c, &msg);
+ break;
+ }
+ n = read(inpipe, buf, bufsize);
+ write(afd, buf, n);
+ }
+}
+
+Dec*
+spawndecoder(char *file)
+{
+ Dec *d;
+ Decodearg *a;
+ Ctlarg *c;
+ int p[2];
+
+ d = emalloc(sizeof(Dec));
+
+ pipe(p);
+ a = emalloc(sizeof(Decodearg));
+ a->cpid = chancreate(sizeof(int), 0);
+ a->outpipe = p[1];
+ a->file = file;
+ procrfork(decodeproc, a, 8192, RFFDG);
+ recv(a->cpid, &(d->decpid));
+ chanfree(a->cpid);
+ free(a);
+
+ c = emalloc(sizeof(Ctlarg));
+ c->ctl = d->ctl = chancreate(sizeof(enum decmsg), 0);
+ c->inpipe = p[0];
+ d->ctlpid = procrfork(ctlproc, c, 8192, RFFDG);
+ /* Other proc frees c */
+
+ close(p[0]);
+ close(p[1]);
+
+ return d;
+}
\ No newline at end of file
--- /dev/null
+++ b/flac.c
@@ -1,0 +1,230 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+
+#include "dat.h"
+#include "fncs.h"
+
+enum blocktype{
+ STREAMINFO,
+ PADDING,
+ APPLICATION,
+ SEEKTABLE,
+ VORBIS_COMMENT,
+ CUESHEET,
+ PICTURE,
+};
+
+FlacPic*
+readFlacPic(int fd, vlong offset)
+{
+ uchar buf[1024];
+ uint len;
+ FlacPic *pic;
+
+ pic = emalloc(sizeof(FlacPic));
+ /* We skip the picture type */
+ offset+=4;
+
+ pread(fd, buf, 4, offset);
+ len = bebtoi(buf, 4);
+ offset+=4;
+
+ pread(fd, buf, len, offset);
+ buf[len] = '\0';
+ pic->mime = strdup((char*)buf);
+ offset+=len;
+
+ pread(fd, buf, 4, offset);
+ len = bebtoi(buf, 4);
+ offset+=4;
+
+ pread(fd, buf, len, offset);
+ buf[len] = '\0';
+ pic->desc = runesmprint("%s", (char*)buf);
+ offset+=len;
+
+ pread(fd, buf, 4, offset);
+ pic->p.x = bebtoi(buf, 4);
+ offset+=4;
+
+ pread(fd, buf, 4, offset);
+ pic->p.y = bebtoi(buf, 4);
+ offset+=4;
+
+ /*We skip color depth and index */
+ offset+=8;
+
+ pread(fd, buf, 4, offset);
+ pic->size = bebtoi(buf, 4);
+ offset+=4;
+
+ pic->data = emalloc(pic->size);
+ pread(fd, pic->data, pic->size, offset);
+
+ return pic;
+}
+
+
+typedef struct{
+ int fd;
+ uvlong size;
+ uchar *data;
+} WriteArg;
+
+void
+picwriteproc(void *arg)
+{
+ int i, n;
+ WriteArg *a = arg;
+ int fd = a->fd;
+ uchar *data = a->data;
+ uvlong towrite = a->size;
+
+ free(a);
+ for(i = 0;towrite>0;){
+ n = write(fd, data+i, towrite);
+ i+=n;
+ towrite-=n;
+ }
+}
+
+typedef struct{
+ int fdin;
+ int fdout;
+ Channel *cpid;
+ char *cmd;
+} ExecArg;
+
+void
+picexecproc(void *arg)
+{
+ ExecArg *a = arg;
+ dup(a->fdin, 0);
+ dup(a->fdout, 1);
+ procexecl(a->cpid, a->cmd, a->cmd, "-c", nil);
+}
+
+void
+picresizeproc(void *arg)
+{
+ ExecArg *a = arg;
+ dup(a->fdin, 0);
+ dup(a->fdout, 1);
+ procexecl(a->cpid, "/bin/resize", "resize", "-x", "256", "-y", "256", nil);
+}
+
+char*
+mime2bin(char *mime)
+{
+ char *slash = strchr(mime, '/');
+ if(slash==nil)
+ return nil;
+ slash+=1;
+
+ if(strcmp(slash, "jpeg") == 0)
+ return strdup("/bin/jpg");
+ if(strcmp(slash, "png") == 0)
+ return strdup("/bin/png");
+
+ return nil;
+}
+
+void
+convFlacPic(FlacPic *pic)
+{
+ ExecArg *e;
+ WriteArg *w;
+ int convin[2];
+ int convout[2];
+ int resize[2];
+
+ pipe(convin);
+ pipe(convout);
+ pipe(resize);
+
+ w = emalloc(sizeof(WriteArg));
+ w->fd = convin[0];
+ w->data = pic->data;
+ w->size = pic->size;
+ procrfork(picwriteproc, w, 8192, RFFDG);
+ /* other proc frees w */
+
+ e = emalloc(sizeof(ExecArg));
+ e->cpid = chancreate(sizeof(int), 0);
+ e->fdin = convin[1];
+ e->fdout = convout[0];
+ e->cmd = mime2bin(pic->mime);
+ procrfork(picexecproc, e, 8192, RFFDG);
+ recv(e->cpid, nil);
+ chanfree(e->cpid);
+ free(e->cmd);
+ free(e);
+
+ e = emalloc(sizeof(ExecArg));
+ e->cpid = chancreate(sizeof(int), 0);
+ e->fdin = convout[1];
+ e->fdout = resize[0];
+ procrfork(picresizeproc, e, 8192, RFFDG);
+ recv(e->cpid, nil);
+ chanfree(e->cpid);
+ free(e);
+
+ pic->i = readimage(display, resize[1], 0);
+ close(convin[0]);
+ close(convin[1]);
+ close(convout[0]);
+ close(convout[1]);
+ close(resize[0]);
+ close(resize[1]);
+}
+
+FlacMeta*
+readFlacMeta(int fd)
+{
+ uvlong off;
+ char type;
+ FlacMeta *f;
+ uchar buf[32];
+
+ pread(fd, buf, 4, 0);
+
+ if(memcmp(buf, "fLaC", 4) != 0){
+ return nil;
+ }
+ off=4;
+
+ f = emalloc(sizeof(FlacMeta));
+ while(pread(fd, buf, 1, off)){
+ type = *buf;
+ off++;
+
+ pread(fd, buf, 3, off);
+ off+=3;
+
+ switch(type & 0x7f){
+ case STREAMINFO:
+ break;
+ case PADDING:
+ break;
+ case APPLICATION:
+ break;
+ case SEEKTABLE:
+ break;
+ case VORBIS_COMMENT:
+ f->com = parseVorbisMeta(fd, off);
+ break;
+ case CUESHEET:
+ break;
+ case PICTURE:
+ f->pic = readFlacPic(fd, off);
+ break;
+ }
+ off+=bebtoi(buf, 3);
+ if((type & 0x80) > 0)
+ break;
+ }
+
+ return f;
+}
\ No newline at end of file
--- /dev/null
+++ b/fncs.h
@@ -1,0 +1,21 @@
+/* dec.c */
+Dec* spawndecoder(char*);
+
+/* mpl.c */
+void quit(char*);
+
+/* util.c */
+void* emalloc(vlong);
+u64int bebtoi(uchar*,int);
+u64int lebtoi(uchar*,int);
+
+/* id3.c */
+ID3v1* readID3v1(int);
+void destroyID3v1(ID3v1 *id);
+
+/* vorbis.c */
+VorbisMeta* parseVorbisMeta(int, uvlong);
+
+/* flac.c */
+FlacMeta* readFlacMeta(int);
+void convFlacPic(FlacPic*);
--- /dev/null
+++ b/id3.c
@@ -1,0 +1,68 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+
+#include "dat.h"
+#include "fncs.h"
+
+ID3v1*
+readID3v1(int fd)
+{
+ Dir *d;
+ ID3v1 *id;
+ char buf[128];
+ char fieldbuf[31];
+ int length;
+
+ d = dirfstat(fd);
+ if(d == nil)
+ return nil;
+ length = d->length;
+ free(d);
+
+ if(pread(fd, buf, 128, length-128) != 128)
+ return nil;
+
+ if(memcmp(buf, "TAG", 3) != 0)
+ return nil;
+
+ id = emalloc(sizeof(ID3v1));
+
+ memcpy(fieldbuf, buf+3, 30);
+ fieldbuf[30] = '\0';
+ id->title = emalloc(sizeof(Rune) * utflen(fieldbuf) + 1);
+ runesprint(id->title, "%s", fieldbuf);
+
+ memcpy(fieldbuf, buf+33, 30);
+ id->artist = emalloc(sizeof(Rune) * utflen(fieldbuf) + 1);
+ runesprint(id->artist, "%s", fieldbuf);
+
+ memcpy(fieldbuf, buf+63, 30);
+ id->album = emalloc(sizeof(Rune) * utflen(fieldbuf) + 1);
+ runesprint(id->album, "%s", fieldbuf);
+
+ memcpy(fieldbuf, buf+93, 4);
+ fieldbuf[4] = '\0';
+ id->year = atoi(fieldbuf);
+
+ memcpy(fieldbuf, buf+97, 30);
+ fieldbuf[30] = '\0';
+ id->comment = emalloc(sizeof(Rune) * utflen(fieldbuf) + 1);
+ runesprint(id->comment, "%s", fieldbuf);
+
+ id->genre = buf[127];
+
+ return id;
+}
+
+void
+destroyID3v1(ID3v1 *id)
+{
+ free(id->title);
+ free(id->artist);
+ free(id->album);
+ free(id->comment);
+ free(id);
+}
+
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,13 @@
+</$objtype/mkfile
+BIN=$home/bin/$objtype
+TARG=mpl
+OFILES=\
+ mpl.$O \
+ dec.$O \
+ util.$O \
+ id3.$O \
+ flac.$O \
+ vorbis.$O \
+
+
+</sys/src/cmd/mkone
--- /dev/null
+++ b/mpl.c
@@ -1,0 +1,128 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+#include <keyboard.h>
+#include <cursor.h>
+#include <mouse.h>
+
+#include "dat.h"
+#include "fncs.h"
+
+enum {
+ MOUSEC,
+ RESIZEC,
+ KEYC,
+ NONE
+};
+
+Dec *d;
+Mousectl *mctl;
+Keyboardctl *kctl;
+Image *cover;
+
+void
+quit(char *err)
+{
+ closedisplay(display);
+ closemouse(mctl);
+ closekeyboard(kctl);
+ threadexitsall(err);
+}
+
+void
+eresized(int isnew)
+{
+ if(isnew && getwindow(display, Refnone) < 0)
+ quit("eresized: Can't reattach to window");
+}
+
+void
+handleaction(Rune kbd)
+{
+ enum decmsg msg;
+ switch(kbd){
+ case Kbs:
+ case Kdel:
+ quit(nil);
+ break;
+ case 'w':
+ draw(screen, screen->r, cover, nil, ZP);
+ break;
+ case 'b':
+ draw(screen, screen->r, display->black, nil, ZP);
+ break;
+ case 'p':
+ msg = PAUSE;
+ send(d->ctl, &msg);
+ break;
+ case 'l':
+ msg = START;
+ send(d->ctl, &msg);
+ break;
+ }
+ flushimage(display, Refnone);
+}
+
+void
+usage(void)
+{
+ fprint(2, "Usage: %s file", argv0);
+ sysfatal("usage");
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+ Mouse mouse;
+ Rune kbd;
+ int resize[2];
+
+ //TODO: Use ARGBEGIN
+ argv0 = argv[0];
+
+ if(argc != 2)
+ usage();
+
+ if(initdraw(nil, nil, "mpl") < 0)
+ sysfatal("%s: Failed to init screen %r", argv0);
+ if((mctl = initmouse(nil, screen)) == nil)
+ sysfatal("initmouse: %r");
+ if((kctl = initkeyboard(nil)) == nil)
+ sysfatal("initkeyboard: %r");
+
+ d = spawndecoder(argv[1]);
+
+ int fd = open(argv[1], OREAD);
+ if(fd < 0)
+ quit("can not open file");
+ FlacMeta *f = readFlacMeta(fd);
+ if(f == nil)
+ print("failed to parse\n");
+ if(f != nil && f->com == nil)
+ print("failed to parse comm\n");
+
+ convFlacPic(f->pic);
+ if(f->pic->i == nil)
+ quit("Could not load image\n");
+ cover = f->pic->i;
+ handleaction('w');
+
+ Alt alts[] = {
+ {mctl->c, &mouse, CHANRCV},
+ {mctl->resizec, resize, CHANRCV},
+ {kctl->c, &kbd, CHANRCV},
+ {nil, nil, CHANEND},
+ };
+
+ for(;;){
+ switch(alt(alts)){
+ case KEYC:
+ handleaction(kbd);
+ break;
+ case RESIZEC:
+ eresized(1);
+ break;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+++ b/util.c
@@ -1,0 +1,44 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+
+#include "dat.h"
+#include "fncs.h"
+
+/* Malloc and memset to 0, quit gracefully on error */
+void*
+emalloc(vlong size)
+{
+ void *v = malloc(size);
+ if(v == nil)
+ quit("Out of memory");
+ v = memset(v, 0, size);
+ return v;
+}
+
+/*
+* Unmarshal LSB ordered buffer into unsigned int
+* https://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html
+*/
+u64int
+lebtoi(uchar *buf, int nbyte)
+{
+ int i;
+ u64int out = 0;
+ for(i=0;i<nbyte;i++)
+ out = out | buf[i]<<(i*8);
+ return out;
+}
+
+/* Unmarshal MSB ordered buffer into unsigned int */
+u64int
+bebtoi(uchar *buf, int nbyte)
+{
+ int i;
+ int n = nbyte;
+ u64int out = 0;
+ for(i=0;i<nbyte;i++)
+ out = out | buf[i]<<(--n*8);
+ return out;
+}
--- /dev/null
+++ b/vorbis.c
@@ -1,0 +1,52 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+
+#include "dat.h"
+#include "fncs.h"
+
+VorbisMeta*
+parseVorbisMeta(int fd, uvlong offset)
+{
+ u32int size;
+ uchar buf[1024];
+ uint i;
+ VorbisMeta *v;
+ char *sep;
+
+ v = emalloc(sizeof(VorbisMeta));
+
+ /* Vendor String */
+ pread(fd, buf, 4, offset);
+ size = lebtoi(buf, 4);
+ offset+=size+4;
+
+ pread(fd, buf, 4, offset);
+ v->ncom = lebtoi(buf, 4);
+ v->key = emalloc(sizeof(Rune*) * v->ncom);
+ v->val = emalloc(sizeof(Rune*) * v->ncom);
+ offset+=4;
+
+ for(i=0;i<v->ncom;i++){
+ pread(fd, buf, 4, offset);
+ size = lebtoi(buf, 4);
+ offset+=4;
+
+ pread(fd, buf, size, offset);
+ offset+=size;
+
+ buf[size] = '\0';
+ sep = strchr((char*)buf, '=');
+ if(sep == nil){
+ fprint(2, "Invalid vorbis header format\n");
+ quit("Invalid vorbis header format");
+ continue;
+ }
+ *sep = '\0';
+ v->key[i] = runesmprint("%s", (char*)buf);
+ v->val[i] = runesmprint("%s", sep+1);
+ }
+
+ return v;
+}
\ No newline at end of file