<?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>’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 <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); } <!-- 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>