/*
* 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);
}
|