1 /* lsmp3.c *************************************** UPDATED: 1999-04-08 23:38 TT
2  *
3  * Copyright   : (C) 1999 Tormod Tjaberg
4  * Author      : Tormod Tjaberg (tjaberg@online.no)
5  * Ripper      : Zeograd (zeograd@caramail.com)
6  * Created     : 1999-02-14
7  * Ripped      : around 1999-11-21
8  * Description : Display the ID3 tag v1.0 & v1.1 of an MP3 file
9  * Author      : Tormod Tjaberg
10  *
11  * Note:
12  * . Were shamelessly ripped from zeograd to know MP3 length for Hu-Go!
13  *
14  * . Genres were taken from Winamp 2.091 using doctored files :)
15  *
16  * . TAG code info is based on ID3-Tag Specification V1.1 by Michael Mutschler
17  *   http://tick.informatik.uni-stuttgart.de/~mutschml/MP3ext/ID3-Tag.html
18  *
19  * . The MP3 header documentation is gleaned from several sources.
20  *   I could not find a definitive source which explained all versions.
21  *
22  *   . The "Private Life Of MP3 frames"
23  *     <http://www.id3.org>
24  *
25  *   . The Perl script "mp3tag.pm" by Peter D. Kovacs sent to me by Ole
26  *     <http://www.egr.uri.edu/~kovacsp>
27  *
28  *   . A lot of the header decoder info is from Oliver Fromme's "mp3asm"
29  *     <http://mpg.123.org>
30  *
31  * Changes:
32  * 1999-04-08: 1.17: Exit properly after displaying "usage" text
33  * 1999-03-07: 1.16: Cosmetic change
34  * 1999-02-26: 1.15: Even more info, restructured layout
35  * 1999-02-26: 1.10: Support for MP3 frame info
36  * 1999-02-14: 1.00: Initial release, TAG's only
37  */
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <stdarg.h>
41 #include <string.h>
42 #include <ctype.h>
43 #include <limits.h>
44 #include <assert.h>
45 
46 #define PROGRAM_NAME "lsmp3"
47 #define PROGRAM_VER  "1.17"
48 
49 #define ID3_TAG_LEN  128
50 #define TAG_LEN      3
51 #define SONGNAME_LEN 30
52 #define ARTIST_LEN   30
53 #define ALBUM_LEN    30
54 #define YEAR_LEN     4
55 #define COMMENT_LEN  30
56 #define NUM_GENRE    148
57 #define FRAME_SIZ    2048
58 
59 /* Clever little option mimic wildcard expansion from
60  * shell ONLY available with the Zortech compiler ver 3.0x and upwards
61  */
62 #if defined(__ZTC__)
63 extern int _cdecl __wildcard;
64 int *__wild = &__wildcard;
65 #endif
66 
67 /* Needs to be at least 4 bytes */
68 typedef unsigned long uint4;
69 
70 /*
71  *   1st index:  0 = "MPEG 1.0",   1 = "MPEG 2.0"
72  *   2nd index:  0 unused,   1 = "Layer 3",   2 = "Layer 2",   3 = "Layer 1"
73  *   3rd index:  BitRateIdx index from frame header
74  */
75 int KbPerSecTab[2][4][16] = {
76   {
77    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
78    {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320},
79    {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384},
80    {0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448}
81    },
82   {
83    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
84    {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160},
85    {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160},
86    {0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256}
87    }
88 };
89 
90 unsigned int FrqTab[] = { 44100, 48000, 32000, 22050, 24000, 16000, 11025 };
91 char *EmphasisTbl[] = { "none", "50/15ms", "unknown", "CCITTj.17" };
92 
93 char
fBufferEmpty(char * pBuffer)94 fBufferEmpty (char *pBuffer)
95 {
96   if (pBuffer == NULL)
97     return 1;
98 
99   for (; isspace (*pBuffer) && *pBuffer != '\0'; pBuffer++)
100     ;
101 
102   if (*pBuffer == '\0')
103     return 1;
104   else
105     return 0;
106 }
107 
108 
109 float
MP3_length(char * argv)110 MP3_length (char *argv)
111 {
112   FILE *fp;
113   char *pFileSpec;
114   long FilePos;
115   char TagStr[TAG_LEN + 1];
116   char SongNameStr[SONGNAME_LEN + 1];
117   char ArtistStr[ARTIST_LEN + 1];
118   char AlbumStr[ALBUM_LEN + 1];
119   char YearStr[YEAR_LEN + 1];
120   char CommentStr[COMMENT_LEN + 1];
121   unsigned char *pBuf;
122   unsigned char *pBufTrav;
123   unsigned char *pBufEnd;
124   int Layer;
125   int BitRateIdx;
126   int Frq;
127   int MajorVer;
128   int MinorVer = 0;
129   int KbPerSec = 0;
130   int Mode;
131   int Emphasis;
132   int fCRC = 0;
133   int fPadding = 0;
134   int fPrivate = 0;
135   int fCopyright = 0;
136   int fOriginal = 0;
137   uint4 Head;
138   long n;
139 /*   long Length; */
140   long TotTime = 0;
141   long TotLength = 0;
142   long TotNum = 0;
143   int i;
144 
145   /* Add a few extra bytes so we may overstep our buffer */
146   if ((pBuf = (unsigned char *) malloc (FRAME_SIZ + 8)) == NULL)
147     return 0;
148 
149   {
150     pFileSpec = argv;
151 
152     if ((fp = fopen (pFileSpec, "rb")) == NULL)
153       return 0;
154 
155     /* Determine if this is an MP3 */
156     n = fread (pBuf, sizeof (char), FRAME_SIZ, fp);
157 
158     if (n <= 128)
159       {
160 	/* Too short */
161 	fclose (fp);
162 	return 0;
163       }
164 
165     /* Use KbPerSec as a sentinel */
166     for (KbPerSec = -1, pBufTrav = pBuf, pBufEnd = pBuf + n;
167 	 pBufTrav != pBufEnd; pBufTrav++)
168       {
169 	/* Search for the sync 12 or 11 bits set !, I'm assuming that
170 	 * the alignment could be anything.
171 	 */
172 	if (pBufTrav[0] == 0xff && (pBufTrav[1] & 0xe0) == 0xe0)
173 	  {
174 	    Head = (uint4) pBufTrav[0] << 24 |
175 	      (uint4) pBufTrav[1] << 16 |
176 	      (uint4) pBufTrav[2] << 8 | (uint4) pBufTrav[3];
177 
178 	    /* Test code from mp3asm */
179 	    if ((Head & 0x0c00) == 0x0c00 || !(Head & 0x060000))
180 	      return 0;
181 
182 	    if ((BitRateIdx = (0x0f & Head >> 12)) == 0)
183 	      return 0;
184 
185 	    if ((Layer = 4 - (0x03 & Head >> 17)) > 3)
186 	      return 0;
187 
188 	    if (Head & 0x00100000)
189 	      {
190 		if (Head & 0x00080000)
191 		  {		/* MPEG 1.0 */
192 		    MajorVer = 1;
193 		    Frq = FrqTab[0x03 & (Head >> 10)];
194 		  }
195 		else
196 		  {		/* MPEG 2.0 */
197 		    MajorVer = 2;
198 		    Frq = FrqTab[3 + (0x03 & (Head >> 10))];
199 		  }
200 		MinorVer = 0;
201 	      }
202 	    else
203 	      {			/* "MPEG 2.5" */
204 		Frq = FrqTab[6];
205 		MajorVer = 2;
206 		MinorVer = 5;
207 	      }
208 
209 	    KbPerSec = KbPerSecTab[MajorVer - 1][4 - Layer][BitRateIdx];
210 
211 	    Mode = 0x03 & (Head >> 6);
212 
213 	    /* If the protecion bit is NOT set then a 16bit CRC follows
214 	     * the frame header
215 	     */
216 	    if (!(Head & 0x10000))
217 	      fCRC = 1;
218 
219 	    if (Head & 0x0200)
220 	      fPadding = 1;
221 
222 	    if (Head & 0x100)
223 	      fPrivate = 1;
224 
225 	    if (Head & 0x08)
226 	      fCopyright = 1;
227 
228 	    if (Head & 0x04)
229 	      fOriginal = 1;
230 
231 	    Emphasis = Head & 0x3;
232 
233 	    break;
234 	  }
235       }
236 
237     /* Is this a valid MP3 file ? */
238     if (KbPerSec == -1)
239       {
240 	fclose (fp);
241 	return 0;
242       }
243 
244     /* Seek to the end of the file */
245     fseek (fp, 0, SEEK_END);
246     FilePos = ftell (fp);
247 
248     return (float) FilePos / ((long) KbPerSec * 125);
249 
250 /*
251       TotTime += Length;
252       TotLength += FilePos;
253       TotNum++;
254 
255 
256       printf("File   : %s (%ldK, %d:%02ds)\n",
257               pFileSpec, FilePos / 1024, (int) (Length / 60), (int) (Length - ((Length / 60) * 60)));
258       printf("Info   : MPEG ver %d.%d layer %d, freq %uHz, KBit/s %d, %s\n",
259               MajorVer, MinorVer, Layer, Frq, KbPerSec, Mode == 3 ? "Mono" : "Stereo");
260 */
261 #if 0
262     /* Just in case you need it :) */
263     printf
264       ("Details: Private=%s CRC's=%s Copyright=%s Original=%s Emphasis=%s\n",
265        fPrivate ? "Yes" : "No", fCRC ? "Yes" : "No",
266        fCopyright ? "Yes" : "No", fOriginal ? "Yes" : "No",
267        EmphasisTbl[Emphasis]);
268 #endif
269 
270     if (pBuf[0] == 'I' && pBuf[1] == 'D' && pBuf[2] == '3')
271       {
272 	fclose (fp);
273 	return 0;
274       }
275 
276     /* Look for the TAG ID go backwards */
277     if (FilePos < ID3_TAG_LEN)
278       {
279 	fclose (fp);
280 	return 0;
281       }
282 
283     /* Position ourselves in front of the TAG */
284     fseek (fp, -ID3_TAG_LEN, SEEK_END);
285 
286     /* Start with a clean slate */
287     TagStr[0] = 0;
288     if (fread (TagStr, sizeof (char), TAG_LEN, fp) != TAG_LEN)
289       {
290 	fclose (fp);
291 	return 0;
292       }
293 
294     if (!(TagStr[0] == 'T' && TagStr[1] == 'A' && TagStr[2] == 'G'))
295       {
296 	fclose (fp);
297 	return 0;
298       }
299 
300     /* Read in all the fields and null terminate them */
301     fread (SongNameStr, sizeof (char), SONGNAME_LEN, fp);
302     SongNameStr[SONGNAME_LEN] = 0;
303     fread (ArtistStr, sizeof (char), ARTIST_LEN, fp);
304     ArtistStr[ARTIST_LEN] = 0;
305     fread (AlbumStr, sizeof (char), ALBUM_LEN, fp);
306     AlbumStr[ALBUM_LEN] = 0;
307     fread (YearStr, sizeof (char), YEAR_LEN, fp);
308     YearStr[YEAR_LEN] = 0;
309     fread (CommentStr, sizeof (char), COMMENT_LEN, fp);
310     CommentStr[COMMENT_LEN] = 0;
311 
312     /* Finally get the Genre */
313     i = getc (fp);
314 
315     fclose (fp);
316 
317     if (i == EOF)
318       return 0;
319 
320     /* All stuff is read in, display it, now instead of storing
321      * 0's a lot of people hve stored space, handle it
322      */
323 /*
324       if (!fBufferEmpty(ArtistStr))
325          printf("Artist : %s\n", ArtistStr);
326 
327       if (!fBufferEmpty(SongNameStr))
328          printf("Title  : %s\n", SongNameStr);
329 
330       if (!fBufferEmpty(AlbumStr))
331          printf("Album  : %s\n", AlbumStr);
332 
333       if (!fBufferEmpty(YearStr))
334          printf("Year   : %s\n", YearStr);
335 
336       if (!fBufferEmpty(CommentStr))
337          printf("Comment: %s\n", CommentStr);
338 */
339 
340     /* Special ID3 ver 1.1 addition */
341 
342 /*
343       if (CommentStr[COMMENT_LEN - 2] == 0 &&
344           CommentStr[COMMENT_LEN - 1] != 0)
345          printf("Track  : %d\n", CommentStr[COMMENT_LEN - 1]);
346 */
347 
348     /* 255 means unused */
349 
350 /*
351       if (i != 255)
352       {
353          if (i >= 0 && i < NUM_GENRE)
354             printf("Genre  : %s\n", pGenreTbl[i]);
355          else
356             printf("Genre  : unknown genre\n");
357       }
358 */
359     putchar ('\n');
360   }
361 
362   if (pBuf != NULL)
363     free (pBuf);
364 
365   if (TotNum > 1)
366     printf ("Total  : %ld files %ldK %d:%02ds\n", TotNum, TotLength / 1024,
367 	    (int) (TotTime / 60), (int) (TotTime - ((TotTime / 60) * 60)));
368 
369   return 0;
370 }
371