<?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:webmaster@plan9.bell-labs.com">webmaster@plan9.bell-labs.com</a>.</span>
</font></center>
</body>
</html>