version 0.1
[FAPG] / fapg.c
1 /*\r
2  * FAPG 0.1 released under GPL\r
3  * http://royale.zerezo.com/fapg/\r
4  */\r
5 \r
6 #include <stdio.h>\r
7 #include <stdlib.h>\r
8 #include <getopt.h>\r
9 #include <dirent.h>\r
10 #include <sys/stat.h>\r
11 #include <sys/types.h>\r
12 #include <string.h>\r
13 #include <limits.h>\r
14 #include <unistd.h>\r
15 #include <ctype.h>\r
16 \r
17 #define MAX 10240\r
18 \r
19 int debug=0;\r
20 int format=0; /* 0 = m3u ; 1 = pls ; 2 = html */\r
21 unsigned char *prefix="";\r
22 int recursive=0;\r
23 int separator='/';\r
24 int skip=0;\r
25 int windows=0;\r
26 unsigned char buffer[MAX];\r
27 \r
28 int counter=0;\r
29 \r
30 unsigned char artist[1024];\r
31 unsigned char title[1024];\r
32 int duration;\r
33 \r
34 unsigned char unix2dos[256]={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,70,35,36,37,38,39,40,41,82,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,84,59,36,61,65,71,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,36,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,36,125,126,127,199,252,233,226,228,224,229,231,234,235,232,239,238,236,196,197,201,230,198,244,246,242,251,249,255,214,220,248,163,216,215,131,225,237,243,250,241,209,170,186,191,174,172,189,188,161,171,187,166,166,166,166,166,193,194,192,169,166,166,43,43,162,165,43,43,45,45,43,45,43,227,195,43,43,45,45,166,45,43,164,240,208,202,203,200,105,205,206,207,43,43,166,220,166,204,175,211,223,212,210,245,213,181,254,222,218,219,217,253,221,175,180,173,177,61,190,182,167,247,184,176,168,183,185,179,178,166,160};\r
35 \r
36 void usage()\r
37 {\r
38   fprintf(stderr,"Usage >> fapg [-b|--backslash] [-d|--debug] [-f|--format=m3u|pls|html] [-o|--output=/path/to/file.m3u] [-p|--prefix=/the/prefix] [-r|--recursive] [-w|--windows] /path/to/mp3/dir1 [/path/to/mp3/dir2 ...]\n");\r
39   exit(1);\r
40 }\r
41 \r
42 void parse_options(int argc,char **argv)\r
43 {\r
44   static char const short_options[]="bdf:o:p:rw";\r
45   static struct option long_options[]=\r
46   {\r
47     {"backslash",no_argument,NULL,'b'},\r
48     {"debug",no_argument,NULL,'d'},\r
49     {"format",required_argument,NULL,'f'},\r
50     {"output",required_argument,NULL,'o'},\r
51     {"prefix",required_argument,NULL,'p'},\r
52     {"recursive",no_argument,NULL,'r'},\r
53     {"windows",no_argument,NULL,'w'}\r
54   };\r
55   int c;\r
56   int option_index=0;\r
57   while ((c=getopt_long(argc,argv,short_options,long_options,&option_index))!=-1)\r
58   {\r
59     switch(c)\r
60     {\r
61       case 'b':\r
62         separator='\\';\r
63         break;\r
64       case 'd':\r
65         debug=1;\r
66         break;\r
67       case 'f':\r
68         if (strcmp(optarg,"m3u")==0)  format=0; else\r
69         if (strcmp(optarg,"pls")==0)  format=1; else\r
70         if (strcmp(optarg,"html")==0) format=2; else\r
71         usage();\r
72         break;\r
73       case 'o':\r
74         close(1);\r
75         if (fopen(optarg,"w")==NULL) { fprintf(stderr,"Error >> unable to open output file : %s\n",optarg); exit(2); }\r
76         break;\r
77       case 'p':\r
78         prefix=malloc(strlen(optarg)+1);\r
79         strcpy(prefix,optarg);\r
80         break;\r
81       case 'r':\r
82         recursive=1;\r
83         break;\r
84       case 'w':\r
85         windows=1;\r
86         break;\r
87       default:\r
88         usage();\r
89     }\r
90   }\r
91 }\r
92 \r
93 void parse_mp3(unsigned char *file)\r
94 {\r
95   int bitrates[2][3][15]=\r
96     {{{0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448},\r
97       {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384},\r
98       {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320}},\r
99      {{0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256},\r
100       {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160},\r
101       {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}}};\r
102   FILE *fic;\r
103   unsigned char *c;\r
104   int lus;\r
105 \r
106   if (debug) fprintf(stderr,"Debug >> parsing mp3 : %s\n",file);\r
107 \r
108   /* read header */\r
109   if ((fic=fopen(file,"r"))==NULL) { fprintf(stderr,"Warning >> can't open file : %s\n",file); return; }\r
110   lus=fread(buffer,1,MAX,fic);\r
111   c=buffer;  \r
112 \r
113   /* try ID3v2 */\r
114   if (buffer[0]=='I' && buffer[1]=='D' && buffer[2]=='3')\r
115   {\r
116     int size;\r
117     int version;\r
118     version=*(buffer+3);\r
119     if (version<2 || version>4)\r
120       fprintf(stderr,"Warning >> ID3 v2.%d not implemented ! trying anyway : %s\n",version,file);\r
121     if (*(buffer+5)!=0)\r
122       fprintf(stderr,"Warning >> specials headers not implemented (%d) ! trying anyway : %s\n",*(buffer+5),file);\r
123     c=buffer+6;\r
124     size=(*c<<24)+(*(c+1)<<16)+(*(c+2)<<8)+(*(c+3));\r
125     if (size>lus) size=lus;\r
126     c+=4;\r
127     if (version==2) while (c<buffer+size-10)\r
128     {\r
129       int size=(*(c+3)<<16)+(*(c+4)<<8)+(*(c+5));\r
130       if (*c==0) break;\r
131       if (strncmp(c,"TT2",3)==0)\r
132       {\r
133         strncpy(title,c+7,size-1);\r
134         title[size-1]='\0';\r
135       }\r
136       if (strncmp(c,"TP1",3)==0)\r
137       {\r
138         strncpy(artist,c+7,size-1);\r
139         artist[size-1]='\0';\r
140       }\r
141       c+=size+6;\r
142     }\r
143     if (version==3 || version==4) while (c<buffer+size-10)\r
144     {\r
145       int size=(*(c+4)<<24)+(*(c+5)<<16)+(*(c+6)<<8)+(*(c+7));\r
146       if (*c==0) break;\r
147       if (strncmp(c,"TIT2",4)==0)\r
148       {\r
149         strncpy(title,c+11,size-1);\r
150         title[size-1]='\0';\r
151       }\r
152       if (strncmp(c,"TPE1",4)==0)\r
153       {\r
154         strncpy(artist,c+11,size-1);\r
155         artist[size-1]='\0';\r
156       }\r
157       c+=size+10;\r
158     }\r
159   }\r
160   \r
161   while (c<buffer+lus-10)\r
162   {\r
163     if (*c==0xFF && (*(c+1)&0xF0)==0xF0)\r
164     {\r
165       int version;\r
166       int lay;\r
167       int bitrate_index;\r
168       int bitrate;\r
169       version=2-(*(c+1)>>3&1);\r
170       lay=4-(*(c+1)>>1&3);\r
171       bitrate_index=*(c+2)>>4&0xF;\r
172       if (version>=1 && version<=2 && lay-1>=0 && lay-1<=2 && bitrate_index>=0 && bitrate_index<=14)\r
173         bitrate=bitrates[version-1][lay-1][bitrate_index];\r
174       else\r
175         bitrate=0;\r
176       if (bitrate!=0)\r
177       {\r
178         fseek(fic,0,SEEK_END);\r
179         duration=(ftell(fic)+buffer-c)/125/bitrate;\r
180       }\r
181       else\r
182         duration=0;\r
183       break;\r
184     }\r
185     c++;\r
186   }\r
187 \r
188   /* try ID3v1 */\r
189   if (strlen(artist)==0 && strlen(title)==0)\r
190   {\r
191     fseek(fic,-128,SEEK_END);\r
192     lus=fread(buffer,1,128,fic);\r
193     if (lus==128 && buffer[0]=='T' && buffer[1]=='A' && buffer[2]=='G')\r
194     {\r
195       strncpy(title,buffer+3,30);\r
196       title[30]='\0';\r
197       c=title+29;\r
198       while (c>title && *c==' ') *(c--)='\0';\r
199       strncpy(artist,buffer+33,30);\r
200       artist[30]='\0';\r
201       c=artist+29;\r
202       while (c>artist && *c==' ') *(c--)='\0';\r
203     }\r
204   }\r
205 \r
206   fclose(fic);\r
207 }  \r
208 \r
209 void parse_ogg(unsigned char *file)\r
210 {\r
211   FILE *fic;\r
212   unsigned char *c;\r
213   int lus;\r
214   int sample_rate;\r
215   int samples;\r
216 \r
217   if (debug) fprintf(stderr,"Debug >> parsing ogg : %s\n",file);\r
218 \r
219   /* read header */\r
220   if ((fic=fopen(file,"r"))==NULL) { fprintf(stderr,"Warning >> can't open file : %s\n",file); return; }\r
221   lus=fread(buffer,1,MAX,fic);\r
222 \r
223   /* try Ogg */\r
224   if (buffer[0]!='O' && buffer[1]!='g' && buffer[2]!='g')\r
225   {\r
226     fprintf(stderr,"Warning >> not a Ogg header : %s\n",file);\r
227     return;\r
228   }\r
229   \r
230   c=buffer+0x28;\r
231   sample_rate=(*c)+(*(c+1)<<8)+(*(c+2)<<16)+(*(c+3)<<24);\r
232 \r
233   while (c<buffer+lus-10)\r
234   {\r
235     int size;\r
236     if (strncmp(c,"TITLE=",6)==0)\r
237     {\r
238       size=*(c-4)+(*(c-3)<<8)+(*(c-2)<<16)+(*(c-1)<<24);\r
239       strncpy(title,c+6,size-6);\r
240       title[size-6]='\0';\r
241       c+=size;\r
242     }\r
243     if (strncmp(c,"ARTIST=",7)==0)\r
244     {\r
245       size=*(c-4)+(*(c-3)<<8)+(*(c-2)<<16)+(*(c-1)<<24);\r
246       strncpy(artist,c+7,size-7);\r
247       artist[size-7]='\0';\r
248       c+=size;\r
249     }\r
250     c++;\r
251   }\r
252   \r
253   fseek(fic,-MAX,SEEK_END);\r
254   lus=fread(buffer,1,MAX,fic);\r
255   c=buffer+lus-1;\r
256   while (strncmp(c,"OggS",4)!=0 && c>buffer) c--;\r
257   if (c!=buffer)\r
258   {\r
259     c+=6;\r
260     samples=(*c)+(*(c+1)<<8)+(*(c+2)<<16)+(*(c+3)<<24);\r
261     duration=samples/sample_rate;\r
262   }\r
263   \r
264   fclose(fic);\r
265 }\r
266 \r
267 void parse_directory(unsigned char *path)\r
268 {\r
269   int i,n;\r
270   struct dirent **namelist;\r
271   unsigned char newpath[PATH_MAX];\r
272   unsigned char *c;\r
273   struct stat infos;\r
274 \r
275   void print_faketitle()\r
276   {\r
277     c=newpath+strlen(newpath);\r
278     while (c>newpath && *c!='/') c--;\r
279     while (c<newpath+strlen(newpath)-5)\r
280     {\r
281       c++;\r
282       if (*c=='_') putchar(' '); else if (windows) putchar(unix2dos[*c]); else putchar(*c);\r
283     }\r
284   }\r
285   \r
286   void print_path()\r
287   {\r
288     printf(prefix);\r
289     for (c=newpath+skip;*c!='\0';c++) if (*c=='/') putchar(separator); else if (windows) putchar(unix2dos[*c]); else putchar(*c);\r
290   }\r
291 \r
292   if (debug) fprintf(stderr,"Debug >> parsing directory : %s\n",path);\r
293   if ((n=scandir(path,&namelist,0,alphasort))<0) { fprintf(stderr,"Warning >> can't open directory : %s\n",path); return; }\r
294   for (i=0;i<n;i++)\r
295   {\r
296     sprintf(newpath,"%s/%s",path,namelist[i]->d_name);\r
297     if (stat(newpath,&infos)!=0) { fprintf(stderr,"Warning >> can't stat file : %s\n",newpath); continue; }\r
298     if (recursive && S_ISDIR(infos.st_mode) && strcmp(namelist[i]->d_name,".")!=0 && strcmp(namelist[i]->d_name,"..")!=0) parse_directory(newpath);\r
299     if (S_ISREG(infos.st_mode))\r
300     {\r
301       unsigned char ext[5];\r
302       int j;\r
303       for (j=0;j<5;j++) ext[j]=tolower(namelist[i]->d_name[strlen(namelist[i]->d_name)-4+j]);\r
304       artist[0]='\0';\r
305       title[0]='\0';\r
306       duration=-2;\r
307       if (strcmp(".mp3",ext)==0) { duration=-1; parse_mp3(newpath); }\r
308       if (strcmp(".ogg",ext)==0) { duration=-1; parse_ogg(newpath); }\r
309       if (strcmp(".wav",ext)==0) { duration=-1; /* parse_wav(newpath); */ }\r
310       \r
311       if (duration!=-2) /* is it an audio file ? */\r
312       {\r
313         counter++;\r
314         switch (format)\r
315         {\r
316           case 0:\r
317             if (duration!=-1)\r
318             {\r
319               printf("#EXTINF:%d,",duration);\r
320               if (strlen(artist)==0 && strlen(title)==0) print_faketitle();\r
321               else printf("%s - %s",artist,title);\r
322               putchar('\n');\r
323             }\r
324             print_path();\r
325             putchar('\n');\r
326             break;\r
327           case 1:\r
328             printf("File%d=",counter);\r
329             print_path();\r
330             putchar('\n');\r
331             printf("Title%d=",counter);\r
332             if (strlen(artist)==0 && strlen(title)==0) print_faketitle();\r
333             else printf("%s - %s",artist,title);\r
334             putchar('\n');\r
335             if (duration!=-1) printf("Length%d=%d\n",counter,duration);\r
336             break;\r
337           case 2:\r
338             printf("<tr><td>%d</td><td>%s</td><td>%s</td><td>",counter,artist,title);\r
339             if (duration==-1) printf("?"); else printf("%d:%s%d",duration/60,duration%60<10?"0":"",duration%60);\r
340             printf("</td></tr>\n");\r
341             break;\r
342         }\r
343       }\r
344     }\r
345     free(namelist[i]);\r
346   }\r
347   free(namelist);\r
348 }\r
349 \r
350 int main(int argc,char **argv)\r
351 {\r
352   parse_options(argc,argv);\r
353   if (optind==argc) usage();\r
354   switch (format)\r
355   {\r
356     case 0:\r
357       printf("#EXTM3U\n");\r
358       break;\r
359     case 1:\r
360       printf("[playlist]\n");\r
361       break;\r
362     case 2:\r
363       printf("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n\n<html>\n\n<head>\n<title>Playlist generated by FAPG 0.1</title>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\" />\n<style type=\"text/css\">\n<!--\n\nbody,td,tr {\n  font-family: Verdana, Arial, Helvetica, sans-serif;\n  font-size: 12px;\n  color: #000000;\n}\n\nbody {\n  background: #ffffff;\n}\n\nth {\n  text-align: center;\n  background: #ffcccc;\n  padding-left: 15px;\n  padding-right: 15px;\n  border: 1px #dd8888 solid;\n}\n\ntd {\n  text-align: center;\n  background: #eeeeee;\n  padding-left: 15px;\n  padding-right: 15px;\n  border: 1px #cccccc solid;\n}\n\nh1 {\n  font-size: 25px;\n}\n\np {\n  font-size: 10px;\n}\n\na {\n  color: #993333;\n  text-decoration: none;\n}\n\na:hover {\n  text-decoration: underline;\n}\n\n-->\n</style>\n</head>\n\n<body>\n\n<h1>Playlist</h1>\n\n<table>\n<tr><th>Entry</th><th>Artist</th><th>Title</th><th>Length</th></tr>\n");\r
364       break;\r
365   }\r
366   for (;optind<argc;optind++)\r
367   {\r
368     if (strlen(prefix)!=0) skip=strlen(argv[optind]);\r
369     parse_directory(argv[optind]);\r
370   }\r
371   switch(format)\r
372   {\r
373     case 1:\r
374       printf("NumberOfEntries=%d\nVersion=2\n",counter);\r
375       break;\r
376     case 2:\r
377       printf("</table>\n\n<p>Playlist generated by <a href=\"http://royale.zerezo.com/fapg/\">FAPG 0.1</a></p>\n\n</body>\n\n</html>");\r
378       break;\r
379   }\r
380   exit(0);\r
381 }\r