/* * See http://swtch.com/juke/COPYRIGHT for copyright and license details. * Slightly modified by mason. */ #include #include #include 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 && psize); id3 = mallocz(sizeof *id3, 1); if(id3 == nil) sysfatal("out of memory"); for(i=0; imagic[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; imagic[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; inf; i++){ f = &id->f[i]; for(j=0; jns; 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; inf; i++){ f = &id->f[i]; if(f->ns == 0 || f->type == nil) continue; for(j=0; jtype) == 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