Plan 9 from Bell Labs’s /usr/web/sources/contrib/mason/mp3info.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


/* 
 * See http://swtch.com/juke/COPYRIGHT for copyright and license details.
 * Slightly modified by mason.
 */
#include <u.h>
#include <libc.h>
#include <bio.h>

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]<<21)|(p[1]<<14)|(p[2]<<7)|p[3];
}

static ulong
gsync3(uchar *p)
{
	return (p[0]<<14)|(p[1]<<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 && p<end; p++){
			r = *p;
			t += runetochar(t, &r);
		}
		*t = '\0';
		if(p<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]<<8);
			t += runetochar(t, &r);
		}
		*t = '\0';
		if(p<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]<<8) | p[1];
			t += runetochar(t, &r);
		}
		*t = '\0';
		if(p < 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->size);
	id3 = mallocz(sizeof *id3, 1);
	if(id3 == nil)
		sysfatal("out of memory");
	for(i=0; i<ntag; ){
		fhdr = (FrameHeader2*)(tag+i);
		if(fhdr->magic[0]!='T' && fhdr->magic[0]!='W'){
			i += FrameHeader2Size;
			i += gsync3(fhdr->size);
			continue;
		}
		if(id3->nf%16==0){
			id3->f = realloc(id3->f, (id3->nf+16)*sizeof(Frame));
			if(id3->f == nil)
				sysfatal("out of memory");
		}
		f = &id3->f[id3->nf];
		id3->nf++;
		memset(f, 0, sizeof *f);
		memmove(f->type, fhdr->magic, 3);
		f->type[3] = '\0';
		f->flags = 0;
		i += FrameHeader2Size;
		nstring = gsync3(fhdr->size);
		string = (uchar*)tag+i;
		estring = string+nstring;
		i += nstring;
		while(string && string < estring){
			if(f->ns%16 == 0){
				f->s = realloc(f->s, (f->ns+16)*sizeof(f->s[0]));
				if(f->s == nil)
					sysfatal("out of memory");
			}
			f->s[f->ns++] = decode(&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<3; i++){
		if((c=Bgetc(b)) != m[i]){
			if(c == -1)
				i--;
			for(; i>=0; i--)
				Bungetc(b);
			return nil;
		}
	}
	memmove(hdr.magic, m, 3);
	if(Bread(b, (char*)&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(&hdr, tag, ntag);

	id3 = mallocz(sizeof *id3, 1);
	if(id3 == nil)
		sysfatal("out of memory");
	for(i=0; i<ntag; ){
		fhdr = (FrameHeader*)(tag+i);
		if(fhdr->magic[0]!='T' && fhdr->magic[0]!='W'){
			i += FrameHeaderSize;
			i += gsync(fhdr->size);
			continue;
		}
		if(id3->nf%16==0){
			id3->f = realloc(id3->f, (id3->nf+16)*sizeof(Frame));
			if(id3->f == nil)
				sysfatal("out of memory");
		}
		f = &id3->f[id3->nf];
		id3->nf++;
		memset(f, 0, sizeof *f);
		memmove(f->type, fhdr->magic, 4);
		f->type[4] = '\0';
		f->flags = (fhdr->flags[0]<<8) | fhdr->flags[1];
		i += FrameHeaderSize;
		nstring = gsync(fhdr->size);
		string = (uchar*)tag+i;
		estring = string+nstring;
		i += nstring;
		while(string && string < estring){
			if(f->ns%16 == 0){
				f->s = realloc(f->s, (f->ns+16)*sizeof(f->s[0]));
				if(f->s == nil)
					sysfatal("out of memory");
			}
			f->s[f->ns++] = decode(&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, &h, HeaderSize) == HeaderSize
		&& 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->type, type);
      f->flags = 0;
      for(q = p + n - 1; q >= p && (*q == ' ' || *q == '\0'); --q);
      f->s = mallocz(sizeof(char *), 1);
      f->s[0] = mallocz(q - p + 2, 1);
      strncpy(f->s[0], p, q - p + 1);
      f->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] && tagbuf[V1Title] != ' ')
      	++ntag;
      if(tagbuf[V1Artist] && tagbuf[V1Artist] != ' ')
      	++ntag;
      if(tagbuf[V1Album] && tagbuf[V1Album] != ' ')
      	++ntag;
      if(tagbuf[V1Year] && tagbuf[V1Year] != ' ')
      	++ntag;
      id3 = mallocz(sizeof *id3, 1);
      if(id3 == nil)
              sysfatal("out of memory");
      id3->nf = ntag;
      id3->f = mallocz(ntag * sizeof(id3->f[0]), 1);
      if(id3->f == nil)
              sysfatal("out of memory");
      f = id3->f;
      if(tagbuf[V1Title] && tagbuf[V1Title] != ' '){
              procv1tag(tagbuf + V1Title, 30, "TIT2", f);
              ++f;
      }
      if(tagbuf[V1Artist] && tagbuf[V1Artist] != ' '){
              procv1tag(tagbuf + V1Artist, 30, "TPE1", f);
              ++f;
      }
      if(tagbuf[V1Album] && tagbuf[V1Album] != ' '){
              procv1tag(tagbuf + V1Album, 30, "TALB", f);
              ++f;
      }
      if(tagbuf[V1Year] && 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<id->nf; i++){
		f = &id->f[i];
		for(j=0; j<f->ns; j++)
			free(f->s[j]);
		free(f->s);
	}
	free(id->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<id->nf; i++){
		f = &id->f[i];
		if(f->ns == 0 || f->type == nil)
			continue;
		for(j=0; j<nelem(tags); j++)
			if(strcmp(tags[j].tag, f->type) == 0){
				if(strcmp(tags[j].name, "track") == 0 || strcmp(tags[j].name, "disc") == 0){
					p = strchr(f->s[0], '/');
					if(p){
						print("%s %s\n", tags[j].name, f->s[0]);
						break;
					}
				}
				print("%s %q\n", tags[j].name, f->s[0]);
				break;
			}
		if(debug && j == nelem(tags))
			print("# %s %q\n", f->type, f->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<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);
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.