<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv=Content-Type content="text/html; charset=utf8">
<title>/usr/web/sources/contrib/mason/mp3info.c - Plan 9 from Bell Labs</title>
<!-- THIS FILE IS AUTOMATICALLY GENERATED. -->
<!-- EDIT sources.tr INSTEAD. -->
</meta>
</head>
<body>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<p style="line-height: 1.2em; margin-left: 1.00in; text-indent: 0.00in; margin-right: 1.00in; margin-top: 0; margin-bottom: 0; text-align: center;">
<span style="font-size: 10pt"><a href="/plan9/">Plan 9 from Bell Labs</a>&rsquo;s /usr/web/sources/contrib/mason/mp3info.c</span></p>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<center><font size=-1>
Copyright © 2009 Alcatel-Lucent.<br />
Distributed under the
<a href="/plan9/license.html">Lucent Public License version 1.02</a>.
<br />
<a href="/plan9/download.html">Download the Plan 9 distribution.</a>
</font>
</center>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<table width="100%" cellspacing=0 border=0><tr><td align="center">
<table cellspacing=0 cellpadding=5 bgcolor="#eeeeff"><tr><td align="left">
<pre>
<!-- END HEADER -->
/*
* See http://swtch.com/juke/COPYRIGHT for copyright and license details.
* Slightly modified by mason.
*/
#include &lt;u.h&gt;
#include &lt;libc.h&gt;
#include &lt;bio.h&gt;

int debug;

typedef struct Header Header;
typedef struct ExtHeader ExtHeader;
typedef struct FrameHeader FrameHeader;
typedef struct FrameHeader2 FrameHeader2;
typedef struct Frame Frame;
typedef struct Id3 Id3;

struct Header {
       char magic[3];  /* "ID3" for header, "3DI" for footer */
       uchar major;
       uchar minor;
       uchar flags;
       uchar size[4];  /* synchsafe (7-bits per byte), excludes header and footer (if present) */
};
enum {
       HeaderSize = 3+1+1+1+4
};

enum {  /* Header.flags */
       FUnsync = 0x80,
       FExtendedHeader = 0x40,
       FExperimental = 0x20,
       FFooter = 0x10,
};

struct ExtHeader {
       uchar size[4];  /* synchsafe */
       uchar nbytes;
       uchar flags;
       uchar data[1];
};

enum {  /* ExtHeader.flags */
       EFUpdate = 0x40,                /* Tag is an update */
       EFCrc = 0x20,                   /* CRC-32 is present */
       EFTagRestrict = 0x10,   /* Tag restrictions */
};

struct FrameHeader {
       char magic[4];  /* identifies type of frame */
       uchar size[4];  /* excludes frame header */
       uchar flags[2];
};
enum {
       FrameHeaderSize = 4+4+2,
       FrameHeader2Size = 3+3,
};

struct FrameHeader2 {
       char magic[3];
       uchar size[3];
};

struct Frame {
       char type[5];
       ushort flags;
       char **s;
       int ns;
       int sz;
};

struct Id3 {
       Frame *f;
       int nf;
};

enum {  /* frame text encoding bytes */
       EncLatin1 = 0x00,
       EncUTF16Little = 0x01,
       EncUTF16Big = 0x02,
       EncUTF8 = 0x03,
};

enum {  /* FrameHeader.flags */
       FFDiscardOnTag = 0x4000,        /* discard if altering tag and this frame is unrecognized */
       FFDiscardOnFile = 0x2000,       /* discard if altering file and this frame is unrecognized */
       FFReadOnly = 0x1000,            /* contents intended to be read only */
       FFGroupInfo = 0x0040,           /* frame contains group information */
       FFCompressed = 0x0008,          /* frame is compressed with deflate */
       FFEncrypted = 0x0004,           /* frame is encrypted */
       FFUnsynched = 0x0002,           /* unsynchronization was applied */
       FFDatalength = 0x0001,          /* frame includes data length indicator */
};

static ulong
gsync(uchar *p)
{
       return (p[0]&lt;&lt;21)|(p[1]&lt;&lt;14)|(p[2]&lt;&lt;7)|p[3];
}

static ulong
gsync3(uchar *p)
{
       return (p[0]&lt;&lt;14)|(p[1]&lt;&lt;7)|p[2];
}

char*
decode(uchar **pstr, uchar *end)
{
       int len;
       char *s;
       char *t;
       uchar *p, *str;
       Rune r;

       str = *pstr;
       p = nil;
       s = nil;
       switch(*str++){
       case EncLatin1:
               s = malloc(UTFmax*strlen((char*)str+1)+1);
               if(s == nil)
                       sysfatal("out of memory");
               for(p=str, t=s; *p &amp;&amp; p&lt;end; p++){
                       r = *p;
                       t += runetochar(t, &amp;r);
               }
               *t = '\0';
               if(p&lt;end)
                       p++;
               break;
       case EncUTF16Little:
               s = malloc(UTFmax*runestrlen((Rune*)(str+1))+1);
               if(s == nil)
                       sysfatal("out of memory");
               for(p=str, t=s; p[0]||p[1]; p+=2){
                       r = p[0] | (p[1]&lt;&lt;8);
                       t += runetochar(t, &amp;r);
               }
               *t = '\0';
               if(p&lt;end)
                       p += 2;
               break;
       case EncUTF16Big:
               s = malloc(UTFmax*runestrlen((Rune*)(str+1))+1);
               if(s == nil)
                       sysfatal("out of memory");
               for(p=str, t=s; p[0]||p[1]; p+=2){
                       r = (p[0]&lt;&lt;8) | p[1];
                       t += runetochar(t, &amp;r);
               }
               *t = '\0';
               if(p &lt; end)
                       p += 2;
               break;
       case EncUTF8:
               p = memchr(str, 0, end-str);
               if(p){
                       p++;
                       len = p-str;
               }else{
                       p = end;
                       len = end-str;
               }
               s = malloc(len+1);
               if(s == nil)
                       sysfatal("out of memory");
               memmove(s, str, len);
               s[len] = 0;
               break;
       }
       *pstr = p;
       return s;
}

Id3*
readtags2(Header *hdr, uchar *tag, int ntag)
{
       uchar *string, *estring;
       int i, nstring;
       Frame *f;
       FrameHeader2 *fhdr;
       Id3 *id3;

       ntag = gsync(hdr-&gt;size);
       id3 = mallocz(sizeof *id3, 1);
       if(id3 == nil)
               sysfatal("out of memory");
       for(i=0; i&lt;ntag; ){
               fhdr = (FrameHeader2*)(tag+i);
               if(fhdr-&gt;magic[0]!='T' &amp;&amp; fhdr-&gt;magic[0]!='W'){
                       i += FrameHeader2Size;
                       i += gsync3(fhdr-&gt;size);
                       continue;
               }
               if(id3-&gt;nf%16==0){
                       id3-&gt;f = realloc(id3-&gt;f, (id3-&gt;nf+16)*sizeof(Frame));
                       if(id3-&gt;f == nil)
                               sysfatal("out of memory");
               }
               f = &amp;id3-&gt;f[id3-&gt;nf];
               id3-&gt;nf++;
               memset(f, 0, sizeof *f);
               memmove(f-&gt;type, fhdr-&gt;magic, 3);
               f-&gt;type[3] = '\0';
               f-&gt;flags = 0;
               i += FrameHeader2Size;
               nstring = gsync3(fhdr-&gt;size);
               string = (uchar*)tag+i;
               estring = string+nstring;
               i += nstring;
               while(string &amp;&amp; string &lt; estring){
                       if(f-&gt;ns%16 == 0){
                               f-&gt;s = realloc(f-&gt;s, (f-&gt;ns+16)*sizeof(f-&gt;s[0]));
                               if(f-&gt;s == nil)
                                       sysfatal("out of memory");
                       }
                       f-&gt;s[f-&gt;ns++] = decode(&amp;string, estring);
               }
       }
       return id3;
}

Id3*
readtags(Biobuf *b)
{
       char m[] = "ID3";
       uchar *string, *estring;
       int c, i, ntag, nstring;
       uchar *tag;
       Frame *f;
       FrameHeader *fhdr;
       Header hdr;
       Id3 *id3;

       for(i=0; i&lt;3; i++){
               if((c=Bgetc(b)) != m[i]){
                       if(c == -1)
                               i--;
                       for(; i&gt;=0; i--)
                               Bungetc(b);
                       return nil;
               }
       }
       memmove(hdr.magic, m, 3);
       if(Bread(b, (char*)&amp;hdr+3, HeaderSize-3) != HeaderSize-3)
               sysfatal("short read in id3 header");

       ntag = gsync(hdr.size);
       tag = mallocz(ntag, 1);
       if(tag == nil)
               sysfatal("out of memory");
       if(Bread(b, tag, ntag) != ntag)
               sysfatal("short read reading tags");

       if(hdr.major == 2)
               return readtags2(&amp;hdr, tag, ntag);

       id3 = mallocz(sizeof *id3, 1);
       if(id3 == nil)
               sysfatal("out of memory");
       for(i=0; i&lt;ntag; ){
               fhdr = (FrameHeader*)(tag+i);
               if(fhdr-&gt;magic[0]!='T' &amp;&amp; fhdr-&gt;magic[0]!='W'){
                       i += FrameHeaderSize;
                       i += gsync(fhdr-&gt;size);
                       continue;
               }
               if(id3-&gt;nf%16==0){
                       id3-&gt;f = realloc(id3-&gt;f, (id3-&gt;nf+16)*sizeof(Frame));
                       if(id3-&gt;f == nil)
                               sysfatal("out of memory");
               }
               f = &amp;id3-&gt;f[id3-&gt;nf];
               id3-&gt;nf++;
               memset(f, 0, sizeof *f);
               memmove(f-&gt;type, fhdr-&gt;magic, 4);
               f-&gt;type[4] = '\0';
               f-&gt;flags = (fhdr-&gt;flags[0]&lt;&lt;8) | fhdr-&gt;flags[1];
               i += FrameHeaderSize;
               nstring = gsync(fhdr-&gt;size);
               string = (uchar*)tag+i;
               estring = string+nstring;
               i += nstring;
               while(string &amp;&amp; string &lt; estring){
                       if(f-&gt;ns%16 == 0){
                               f-&gt;s = realloc(f-&gt;s, (f-&gt;ns+16)*sizeof(f-&gt;s[0]));
                               if(f-&gt;s == nil)
                                       sysfatal("out of memory");
                       }
                       f-&gt;s[f-&gt;ns++] = decode(&amp;string, estring);
               }
       }
       return id3;
}

void
usage(void)
{
       fprint(2, "usage: mp3info file.mp3...\n");
       exits("usage");
}

Id3*
gettags(Biobuf *b)
{
       Id3 *id;
       Header h;

       id = readtags(b);
       if(id == nil){
               Bseek(b, -HeaderSize, 2);
               if(Bread(b, &amp;h, HeaderSize) == HeaderSize
               &amp;&amp; memcmp(h.magic, "3DI", 3) == 0){
                       Bseek(b, -HeaderSize-gsync(h.size)-HeaderSize, 2);
                       id = readtags(b);
               }
       }
       return id;
}

enum
{
       V1Title = 3,
       V1Artist = 33,
       V1Album = 63,
       V1Year = 93,
       V1Comment = 97,
       V1Track = 126,
       V1Genre = 127,
       V1Size = 128
};

void
procv1tag(char *p, int n, char *type, Frame *f)
{
     char *q;

     strcpy(f-&gt;type, type);
     f-&gt;flags = 0;
     for(q = p + n - 1; q &gt;= p &amp;&amp; (*q == ' ' || *q == '\0'); --q);
     f-&gt;s = mallocz(sizeof(char *), 1);
     f-&gt;s[0] = mallocz(q - p + 2, 1);
     strncpy(f-&gt;s[0], p, q - p + 1);
     f-&gt;ns = 1;
}

Id3*
readv1tags(Biobuf *b)
{
     char tagbuf[V1Size];
     int ntag;
     Frame *f;
     Id3 *id3;

     Bseek(b, -V1Size, 2);
     if(Bread(b, tagbuf, V1Size) != V1Size)
             sysfatal("Short read for v1 tag");
     if(strncmp(tagbuf, "TAG", 3)){
             Bseek(b, 0, 0);
             return nil;
     }
     ntag = 0;
     if(tagbuf[V1Title] &amp;&amp; tagbuf[V1Title] != ' ')
       ++ntag;
     if(tagbuf[V1Artist] &amp;&amp; tagbuf[V1Artist] != ' ')
       ++ntag;
     if(tagbuf[V1Album] &amp;&amp; tagbuf[V1Album] != ' ')
       ++ntag;
     if(tagbuf[V1Year] &amp;&amp; tagbuf[V1Year] != ' ')
       ++ntag;
     id3 = mallocz(sizeof *id3, 1);
     if(id3 == nil)
             sysfatal("out of memory");
     id3-&gt;nf = ntag;
     id3-&gt;f = mallocz(ntag * sizeof(id3-&gt;f[0]), 1);
     if(id3-&gt;f == nil)
             sysfatal("out of memory");
     f = id3-&gt;f;
     if(tagbuf[V1Title] &amp;&amp; tagbuf[V1Title] != ' '){
             procv1tag(tagbuf + V1Title, 30, "TIT2", f);
             ++f;
     }
     if(tagbuf[V1Artist] &amp;&amp; tagbuf[V1Artist] != ' '){
             procv1tag(tagbuf + V1Artist, 30, "TPE1", f);
             ++f;
     }
     if(tagbuf[V1Album] &amp;&amp; tagbuf[V1Album] != ' '){
             procv1tag(tagbuf + V1Album, 30, "TALB", f);
             ++f;
     }
     if(tagbuf[V1Year] &amp;&amp; tagbuf[V1Year] != ' '){
             procv1tag(tagbuf + V1Year, 4, "TYER", f);
     }
     return id3;
}

void
freetags(Id3 *id)
{
       int i, j;
       Frame *f;

       if(id == nil)
               return;
       for(i=0; i&lt;id-&gt;nf; i++){
               f = &amp;id-&gt;f[i];
               for(j=0; j&lt;f-&gt;ns; j++)
                       free(f-&gt;s[j]);
               free(f-&gt;s);
       }
       free(id-&gt;f);
       free(id);
}

static struct {
       char *tag;
       char *name;
} tags[] = {
       "TALB", "album",
       "TCOM", "composer",
       "TEXT", "lyricist",
       "TIT2", "title",
       "TYER", "year",
       "TPE1", "artist",

       /* ID3 v2 */
       "TAL",  "album",
       "TCM",  "composer",
       "TEXT", "lyricist",
       "TT2",  "title",
       "TYE",  "year",
       "TP1",  "artist",
       "TRK",  "track",
       "TPA",  "disc",
};

void
printtags(Id3 *id)
{
       int i, j;
       char *p;
       Frame *f;

       for(i=0; i&lt;id-&gt;nf; i++){
               f = &amp;id-&gt;f[i];
               if(f-&gt;ns == 0 || f-&gt;type == nil)
                       continue;
               for(j=0; j&lt;nelem(tags); j++)
                       if(strcmp(tags[j].tag, f-&gt;type) == 0){
                               if(strcmp(tags[j].name, "track") == 0 || strcmp(tags[j].name, "disc") == 0){
                                       p = strchr(f-&gt;s[0], '/');
                                       if(p){
                                               print("%s %s\n", tags[j].name, f-&gt;s[0]);
                                               break;
                                       }
                               }
                               print("%s %q\n", tags[j].name, f-&gt;s[0]);
                               break;
                       }
               if(debug &amp;&amp; j == nelem(tags))
                       print("# %s %q\n", f-&gt;type, f-&gt;s[0]);
       }
}

void
main(int argc, char **argv)
{
       int i;
       Id3 *id;
       Biobuf *b;

       ARGBEGIN{
       case 'd':
               debug = 1;
               break;
       default:
               usage();
       }ARGEND

       doquote = needsrcquote;
       quotefmtinstall();
       for(i=0; i&lt;argc; i++){
               if((b = Bopen(argv[i], OREAD)) == nil)
                       continue;
               id = gettags(b);
               if(id == nil)
                       id = readv1tags(b);
               if(id == nil){
                       Bterm(b);
                       continue;
               }
               printtags(id);
               freetags(id);
               Bterm(b);
       }
       exits(nil);
}
<!-- BEGIN TAIL -->
</pre>
</td></tr></table>
</td></tr></table>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<p style="line-height: 1.2em; margin-left: 1.00in; text-indent: 0.00in; margin-right: 1.00in; margin-top: 0; margin-bottom: 0; text-align: center;">
<span style="font-size: 10pt"></span></p>
<p style="margin-top: 0; margin-bottom: 0.50in"></p>
<p style="margin-top: 0; margin-bottom: 0.33in"></p>
<center><table border="0"><tr>
<td valign="middle"><a href="http://www.alcatel-lucent.com/"><img border="0" src="/plan9/img/logo_ft.gif" alt="Bell Labs" />
</a></td>
<td valign="middle"><a href="http://www.opensource.org"><img border="0" alt="OSI certified" src="/plan9/img/osi-certified-60x50.gif" />
</a></td>
<td><img style="padding-right: 45px;" alt="Powered by Plan 9" src="/plan9/img/power36.gif" />
</td>
</tr></table></center>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<center>
<span style="font-size: 10pt">(<a href="/plan9/">Return to Plan 9 Home Page</a>)</span>
</center>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<center><font size=-1>
<span style="font-size: 10pt"><a href="http://www.lucent.com/copyright.html">Copyright</a></span>
<span style="font-size: 10pt">© 2009 Alcatel-Lucent.</span>
<span style="font-size: 10pt">All Rights Reserved.</span>
<br />
<span style="font-size: 10pt">Comments to</span>
<span style="font-size: 10pt"><a href="mailto:[email protected]">[email protected]</a>.</span>
</font></center>
</body>
</html>