version 0.36 (by Hank Barta)
[FAPG] / fapg.c
1 /*
2  * FAPG means Fast Audio Playlist Generator.
3  * It is a tool to generate list of audio files (Wav, MP3, Ogg, etc)
4  * in various formats (M3U, PLS, HTML, etc).
5  * It is very usefull if you have a large amount of audio files
6  * and you want to quickly and frequently build a playlist.
7  *
8  * Copyright (C) 2003-2004  Antoine Jacquet <royale@zerezo.com>
9  * http://royale.zerezo.com/fapg/
10  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
24  */
25
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <getopt.h>
29 #include <dirent.h>
30 #include <sys/stat.h>
31 #include <sys/types.h>
32 #include <string.h>
33 #include <strings.h>
34 #include <limits.h>
35 #include <unistd.h>
36 #include <ctype.h>
37 #include <time.h>
38 #include <assert.h>
39 #include "genres.h"
40
41 #define VERSION "0.36"
42 #define MP3_BASE 1024
43 #define OGG_BASE 1024*10
44 #define MAX 1024*200            /* 200ko for ID3 with JPEG images in it */
45
46 int debug = 0;
47 int format = 0;                 /* 0 = m3u ; 1 = pls ; 2 = html ; 3 = rss */
48 char *genrelist = NULL;
49 unsigned char *prefix = "";
50 unsigned char *base = "";
51 unsigned char *dir = "";
52 unsigned char *hostname = "fritzserver.de";
53 // unsigned char *referal="/usr/local/bin/fapg-rss.sh";
54 unsigned char *referal = NULL;
55 //int windows=0;
56 int recursive = 0;
57 int avoidhlinked = 0;
58 int separator = '/';
59 unsigned char *eol = "\n";
60 unsigned char buffer[MAX];
61
62 int counter = 0;
63
64 unsigned char artist[1024];
65 unsigned char title[1024];
66 unsigned char genrebuf[1024];
67 unsigned char genre = 0;
68 int duration;
69 #define MP2ENC 1
70 #define MP3ENC 2
71 #define MPCENC 3
72 #define MPPENC 4
73 #define OGGENC 5
74 #define WAVENC 6
75
76 char *magic[] = { NULL,
77     "audio/mpeg", "audio/mpeg",
78     "audio/mpeg", "audio/mpeg",
79     "audio/ogg-vorbis", "audio/x-wav",
80     NULL
81 };
82
83 unsigned char unix2dos[] =
84     { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
85     16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
86     32, 33, 70, 35, 36, 37, 38, 39, 40, 41, 82, 43, 44, 45, 46, 47,
87     48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 84, 59, 36, 61, 65, 71,
88     64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
89     80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 36, 93, 94, 95,
90     96, 97, 98, 99, 100, 101, 102, 103,
91     104, 105, 106, 107, 108, 109, 110, 111,
92     112, 113, 114, 115, 116, 117, 118, 119,
93     120, 121, 122, 123, 36, 125, 126, 127,
94     199, 252, 233, 226, 228, 224, 229, 231,
95     234, 235, 232, 239, 238, 236, 196, 197,
96     201, 230, 198, 244, 246, 242, 251, 249,
97     255, 214, 220, 248, 163, 216, 215, 131,
98     225, 237, 243, 250, 241, 209, 170, 186,
99     191, 174, 172, 189, 188, 161, 171, 187,
100     166, 166, 166, 166, 166, 193, 194, 192,
101     169, 166, 166, 43, 43, 162, 165, 43,
102     43, 45, 45, 43, 45, 43, 227, 195,
103     43, 43, 45, 45, 166, 45, 43, 164,
104     240, 208, 202, 203, 200, 105, 205, 206,
105     207, 43, 43, 166, 220, 166, 204, 175,
106     211, 223, 212, 210, 245, 213, 181, 254,
107     222, 218, 219, 217, 253, 221, 175, 180,
108     173, 177, 61, 190, 182, 167, 247, 184,
109     176, 168, 183, 185, 179, 178, 166, 160
110 };
111
112 unsigned char *basemap;
113 unsigned char *winorunix;
114 unsigned char one2one[] =
115     { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
116     16, 17, 18, 19, 20, 21, 22, 23,
117     24, 25, 26, 27, 28, 29, 30, 31,
118     32, 33, 34, 35, 36, 37, 38, 39,
119     40, 41, 42, 43, 44, 45, 46, 47,
120     48, 49, 50, 51, 52, 53, 54, 55,
121     56, 57, 58, 59, 60, 61, 62, 63,
122     64, 65, 66, 67, 68, 69, 70, 71,
123     72, 73, 74, 75, 76, 77, 78, 79,
124     80, 81, 82, 83, 84, 85, 86, 87,
125     88, 89, 90, 91, 92, 93, 94, 95,
126     96, 97, 98, 99, 100, 101, 102, 103,
127     104, 105, 106, 107, 108, 109, 110, 111,
128     112, 113, 114, 115, 116, 117, 118, 119,
129     120, 121, 122, 123, 124, 125, 126, 127,
130     128, 129, 130, 131, 132, 133, 134, 135,
131     136, 137, 138, 139, 140, 141, 142, 143,
132     144, 145, 146, 147, 148, 149, 150, 151,
133     152, 153, 154, 155, 156, 157, 158, 159,
134     160, 161, 162, 163, 164, 165, 166, 167,
135     168, 169, 170, 171, 172, 173, 174, 175,
136     176, 177, 178, 179, 180, 181, 182, 183,
137     184, 185, 186, 187, 188, 189, 190, 191,
138     192, 193, 194, 195, 196, 197, 198, 199,
139     200, 201, 202, 203, 204, 205, 206, 207,
140     208, 209, 210, 211, 212, 213, 214, 215,
141     216, 217, 218, 219, 220, 221, 222, 223,
142     224, 225, 226, 227, 228, 229, 230, 231,
143     232, 233, 234, 235, 236, 237, 238, 239,
144     240, 241, 242, 243, 244, 245, 246, 247,
145     248, 249, 250, 251, 252, 253, 254, 255
146 };                              /* identical mapping */
147
148 unsigned char noand[256] =
149     { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
150     16, 17, 18, 19, 20, 21, 22, 23,
151     24, 25, 26, 27, 28, 29, 30, 31,
152     32, 33, 34, 35, 36, 37, 43, 39,
153     40, 41, 42, 43, 44, 45, 46, 47,
154     48, 49, 50, 51, 52, 53, 54, 55,
155     56, 57, 58, 59, 60, 61, 62, 63,
156     64, 65, 66, 67, 68, 69, 70, 71,
157     72, 73, 74, 75, 76, 77, 78, 79,
158     80, 81, 82, 83, 84, 85, 86, 87,
159     88, 89, 90, 91, 92, 93, 94, 95,
160     96, 97, 98, 99, 100, 101, 102, 103,
161     104, 105, 106, 107, 108, 109, 110, 111,
162     112, 113, 114, 115, 116, 117, 118, 119,
163     120, 121, 122, 123, 124, 125, 126, 127,
164     128, 129, 130, 131, 132, 133, 134, 135,
165     136, 137, 138, 139, 140, 141, 142, 143,
166     144, 145, 146, 147, 148, 149, 150, 151,
167     152, 153, 154, 155, 156, 157, 158, 159,
168     160, 161, 162, 163, 164, 165, 166, 167,
169     168, 169, 170, 171, 172, 173, 174, 175,
170     176, 177, 178, 179, 180, 181, 182, 183,
171     184, 185, 186, 187, 188, 189, 190, 191,
172     192, 193, 194, 195, 196, 197, 198, 199,
173     200, 201, 202, 203, 204, 205, 206, 207,
174     208, 209, 210, 211, 212, 213, 214, 215,
175     216, 217, 218, 219, 220, 221, 222, 223,
176     224, 225, 226, 227, 228, 229, 230, 231,
177     232, 233, 234, 235, 236, 237, 238, 239,
178     240, 241, 242, 243, 244, 245, 246, 247,
179     248, 249, 250, 251, 252, 253, 254, 255
180 };                              /* only '&' is mapped to '+' */
181
182 unsigned char *iso2web[256] = {
183     "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07",
184     "%08", "%09", "%0a", "%0b", "%0c", "%0d", "%0e", "%0f",
185     "%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17",
186     "%18", "%19", "%1a", "%1b", "%1c", "%1d", "%1e", "%1f",
187     "%20", "%21", "%22", "%23", "%24", "%25", "%26", "%27",
188     "%28", "%29", "%2a", "+", ",", "-", ".", "/",
189     "0", "1", "2", "3", "4", "5", "6", "7",
190     "8", "9", ":", ";", "%3c", "=", "%3e", "%3f",
191     "@", "A", "B", "C", "D", "E", "F", "G",
192     "H", "I", "J", "K", "L", "M", "N", "O",
193     "P", "Q", "R", "S", "T", "U", "V", "W",
194     "X", "Y", "Z", "%5B", "\\", "%5D", "^", "_",
195     "`", "a", "b", "c", "d", "e", "f", "g",
196     "h", "i", "j", "k", "l", "m", "n", "o",
197     "p", "q", "r", "s", "t", "u", "v", "w",
198     "x", "y", "z", "%7b", "|", "%7d", "~", "%7f",
199     "%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87",
200     "%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f",
201     "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97",
202     "%98", "%99", "%9a", "%9b", "%9c", "%9d", "%9e", "%9f",
203     "%a0", "%a1", "%a2", "%a3", "%a4", "%a5", "%a6", "%a7",
204     "%a8", "%a9", "%aa", "%ab", "%ac", "%ad", "%ae", "%af",
205     "%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7",
206     "%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf",
207     "%c0", "%c1", "%c2", "%c3", "%c4", "%c5", "%c6", "%c7",
208     "%c8", "%c9", "%ca", "%cb", "%cc", "%cd", "%ce", "%cf",
209     "%d0", "%d1", "%d2", "%d3", "%d4", "%d5", "%d6", "%d7",
210     "%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df",
211     "%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7",
212     "%e8", "%e9", "%ea", "%eb", "%ec", "%ed", "%ee", "%ef",
213     "%f0", "%f1", "%f2", "%f3", "%f4", "%f5", "%f6", "%f7",
214     "%f8", "%f9", "%fa", "%fb", "%fc", "%fd", "%fe", "%ff"
215 };
216
217 void usage()
218 {
219     fprintf(stderr,
220             "Usage >> fapg [-b|--backslash] [-d|--debug] [-f|--format=m3u|pls|html|rss|pla] [-g|--genre=#:#:...] [-n|--nohardlink] [-o|--output=/path/to/file.m3u] [-p|--prefix=/the/prefix] [-r|--recursive] [-w|--windows] [-c|--command=<intern|....>] [-x|--exclude=#:#:...] /path/to/mp3/dir1 [/path/to/mp3/dir2 ...]\n");
221     exit(1);
222 }
223
224 #define mywebputchar(x) { fputs(iso2web[(unsigned char)winorunix[(unsigned char)x]], stdout); }
225 #define    myputchar(x) { putchar(basemap[(unsigned char)winorunix[(unsigned char)x]]); }
226 /* #define    myplaputchar(x) { putchar(basemap[(unsigned char)winorunix[(unsigned char)x]]);putchar('\0');} */
227 void myplaputchar(const char x)
228 {
229     putchar(basemap[(unsigned char)winorunix[(unsigned char)x]]);
230     putchar('\0');
231 }
232
233 void mywebputstr(const char *c)
234 {
235     while(*c != 0) {
236         mywebputchar(*c);
237         c++;
238     }
239 }
240
241 void myplaputstr(const char *c)
242 {
243     while(*c != 0) {
244         if( *c == '/')
245             myplaputchar('\\'); /* translate slash to backslash */
246         else
247             myplaputchar(*c);
248         c++;
249         /* remove multiple slashes "//" when parsing a directory ending with a "/" */
250         while(*c == '/' && c[1] == '/')
251             c++;
252     }
253 }
254
255 void myputstr(const char *c)
256 {
257     while(*c != 0) {
258         myputchar(*c);
259         c++;
260         /* remove multiple slashes "//" when parsing a directory ending with a "/" */
261         while(*c == '/' && c[1] == '/')
262             c++;
263     }
264 }
265
266 void print_webpath(const char *path)
267 {
268     const char *c = path;
269
270     printf(prefix);             /* we must not modify this part */
271     if(*c == '.' && c[1] == '/') {      /* remove leading "./" when parsing current directory */
272         c += 2;
273         /* maybe there follow many slashes */
274         while(*c == '/')
275             c++;
276     }
277     for(; *c != '\0'; c++) {
278         mywebputchar(*c);
279         /* remove multiple "//" when parsing a directory ending with a "/" */
280         while(*c == '/' && c[1] == '/')
281             c++;
282     }
283 }
284
285 void print_path(const char *path)
286 {
287     const char *c = path;
288     printf(prefix);
289     /* skip leading "./" when parsing current directory */
290     if(*c == '.' && *(c + 1) == '/') {
291         c += 2;
292         /* maybe there follow more slashes */
293         while(*c == '/')
294             c++;
295     }
296     myputstr(c);
297 }
298
299 void print_pathtail(const char *path)
300 {
301     const char *c;
302     c = strrchr(path, separator);
303     if(c != NULL)
304         c++;
305     else
306         c = path;
307     myputstr(c);
308 }
309
310 void noreferal(const char *path, const char *artist, const char *title)
311 {
312     printf("\t\t<description><![CDATA[<h4>");
313     myputstr(artist);
314     printf("</h4><h5>");
315     myputstr(title);
316     printf("</h5><a href=\"");
317     print_webpath(path);
318     printf
319         ("\"><br><br>Direct Link to Audiofile</a><br>]]></description>%s",
320          eol);
321 }
322
323 void reference(const char *title)
324 {
325     FILE *pipe = NULL;
326     static char command[2048], buffer[1024];
327     int buflen = 8192;
328
329     buflen = strlen(title) + strlen(referal) + 3;
330     assert((buflen < 2046));
331     strcpy(command, referal);
332     buflen = strlen(command);
333     command[buflen] = ' ';
334     command[buflen + 1] = '"';
335     command[buflen + 2] = 0;
336     strcat(command, title);
337     buflen = strlen(command);
338     command[buflen] = '"';
339     command[buflen + 1] = 0;
340     if(debug)
341         fprintf(stderr, "Debug >> processing command: %s\n", command);
342     pipe = popen(command, "r");
343     if(pipe == NULL) {
344         fprintf(stderr, "Warning >> can't open pipe >%s< !\n", command);
345         free(command);
346         return;
347     }
348     fgets(buffer, 1020, pipe);
349     while(!feof(pipe)) {
350         fputs(buffer, stdout);
351         fgets(buffer, 1020, pipe);
352     }
353     pclose(pipe);
354     return;
355 }
356
357 void parse_options(int argc, char **argv)
358 {
359     static char const short_options[] = "c:bdf:g:lo:np:ruwx:";
360     static struct option long_options[] = {
361         {"backslash", no_argument, NULL, 'b'},
362         {"command", required_argument, NULL, 'b'},
363         {"debug", no_argument, NULL, 'd'},
364         {"format", required_argument, NULL, 'f'},
365         {"genre", required_argument, NULL, 'g'},
366         {"nohardlink", no_argument, NULL, 'n'},
367         {"output", required_argument, NULL, 'o'},
368         {"prefix", required_argument, NULL, 'p'},
369         {"recursive", no_argument, NULL, 'r'},
370         {"windows", no_argument, NULL, 'w'},
371         {"exclude", required_argument, NULL, 'x'}
372     };
373     int c;
374     int option_index = 0;
375     while((c =
376            getopt_long(argc, argv, short_options, long_options,
377                        &option_index)) != -1) {
378         switch (c) {
379         case 'b':
380             separator = '\\';
381             noand['/'] = '\\';
382             break;
383         case 'c':
384             if(strncmp(optarg, "intern", 6) == 0)
385                 referal = NULL;
386             else
387                 referal = strdup(optarg);
388             break;
389         case 'd':
390             debug = 1 - debug;
391             break;
392         case 'f':
393             if(strcmp(optarg, "m3u") == 0)
394                 format = 0;
395             else if(strcmp(optarg, "pls") == 0)
396                 format = 1;
397             else if(strcmp(optarg, "html") == 0)
398                 format = 2;
399             else if(strcmp(optarg, "rss") == 0)
400                 format = 3;
401             else if(strcmp(optarg, "pla") == 0)
402                 format = 4;
403             else
404                 usage();
405             break;
406         case 'g':
407             if(genrelist == NULL)
408                 genrelist = calloc(257, sizeof(char));  /* allow multiple includes/excludes */
409             if(genrelist == NULL) {
410                 fprintf(stderr,
411                         "Error >> unable to allocate cleared memory\n");
412                 exit(2);
413             } else {
414                 unsigned int n = 0;
415                 while(n < strlen(optarg)) {
416                     if(debug)
417                         fprintf(stderr,
418                                 "Debug >> genrelist entry activting : %d\n",
419                                 atoi(&optarg[n]));
420                     genrelist[atoi(&optarg[n])] = 1;
421                     while(isdigit(optarg[n++]));
422                 }
423             }
424             break;
425         case 'n':
426             avoidhlinked = 1;
427             break;
428         case 'o':
429             close(1);
430             if(fopen(optarg, "w") == NULL) {
431                 fprintf(stderr,
432                         "Error >> unable to open output file : %s\n",
433                         optarg);
434                 exit(2);
435             }
436             break;
437         case 'p':
438             prefix = malloc(strlen(optarg) + 1);
439             strcpy(prefix, optarg);
440             base = malloc(strlen(prefix) + 1);
441             strcpy(base, prefix);
442             dir = strchr(base, '/');
443             if((dir != NULL) && (dir[1] == '/'))
444                 dir = strchr(dir + 2, '/');
445             if(dir != NULL)
446                 *dir++ = 0;
447             else
448                 dir = "";
449             /* if prefix is a weblink, base is the baselink, dir is the path */
450             break;
451         case 'r':
452             recursive = 1;
453             break;
454         case 'u':
455             winorunix = one2one;
456             eol = "\n";
457             break;
458         case 'w':
459             winorunix = unix2dos;
460             eol = "\r\n";
461             break;
462         case 'x':
463             if(genrelist == NULL) {     /* allow multiple includes/excludes - not recommended (confusing) but possible */
464                 int n = 0;
465                 genrelist = calloc(257, sizeof(char));
466                 while(n < 256)
467                     genrelist[n++] = 1;
468             }
469             if(genrelist == NULL) {
470                 fprintf(stderr,
471                         "Error >> unable to allocate cleared memory\n");
472                 exit(2);
473             } else {
474                 unsigned int n = 0;
475                 while(n < strlen(optarg)) {
476                     if(debug)
477                         fprintf(stderr,
478                                 "Debug >> genrelist entry activting : %d\n",
479                                 atoi(&optarg[n]));
480                     genrelist[atoi(&optarg[n])] = 0;
481                     while(isdigit(optarg[n++]));
482                 }
483             }
484             break;
485         default:
486             usage();
487         }
488     }
489     /* hostname = getenv("HOSTNAME"); */
490     if(genrelist == NULL) {
491         genrelist = calloc(257, sizeof(char));
492         if(genrelist == NULL) {
493             fprintf(stderr,
494                     "Error >> unable to allocate cleared memory\n");
495             exit(2);
496         } else {
497             int n = 0;
498             while(n < 256)
499                 genrelist[n++] = 1;
500         }
501     }
502 }
503
504 void parse_mp3(unsigned char *file)
505 {
506     int bitrates[2][3][15] =
507         { {{0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384,
508             416, 448},
509            {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320,
510             384},
511            {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256,
512             320}},
513     {{0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256},
514      {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160},
515      {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}}
516     };
517     FILE *fic;
518     unsigned char *c;
519     int lus;
520
521     genre = 0;
522     genrebuf[0] = 0;
523     if(debug)
524         fprintf(stderr, "Debug >> parsing mp3 : %s\n", file);
525
526     /* read header */
527     if((fic = fopen(file, "r")) == NULL) {
528         fprintf(stderr, "Warning >> can't open file : %s\n", file);
529         return;
530     }
531     lus = fread(buffer, 1, MP3_BASE, fic);
532     c = buffer;
533
534     /* try ID3v2 */
535     if(buffer[0] == 'I' && buffer[1] == 'D' && buffer[2] == '3') {
536         int size;
537         int version;
538         version = *(buffer + 3);
539         if(version < 2 || version > 4)
540             fprintf(stderr,
541                     "Warning >> ID3 v2.%d not implemented ! trying anyway : %s\n",
542                     version, file);
543         if(*(buffer + 5) != 0)
544             fprintf(stderr,
545                     "Warning >> specials headers not implemented (%d) ! trying anyway : %s\n",
546                     *(buffer + 5), file);
547         c = buffer + 6;
548         size =
549             (*c << 21) + (*(c + 1) << 14) + (*(c + 2) << 7) + (*(c + 3));
550         /* read more header */
551         if(size + lus > MAX) {
552             lus += fread(buffer + lus, 1, MAX - lus, fic);
553             fprintf(stderr,
554                     "Warning >> ID3 header is huge (%d bytes) ! trying anyway : %s\n",
555                     size, file);
556         } else
557             lus += fread(buffer + lus, 1, size, fic);
558         if(size > lus)
559             size = lus;
560         c += 4;
561         if(version == 2)
562             while(c < buffer + size) {
563                 int size = (*(c + 3) << 16) + (*(c + 4) << 8) + (*(c + 5));
564                 if(*c == 0)
565                     break;
566                 if(strncmp(c, "TT2", 3) == 0) {
567                     strncpy(title, c + 7, size - 1);
568                     title[size - 1] = '\0';
569                 }
570                 if(strncmp(c, "TP1", 3) == 0) {
571                     strncpy(artist, c + 7, size - 1);
572                     artist[size - 1] = '\0';
573                 }
574                 if(strncmp(c, "TCO", 3) == 0) {
575                     /* strncpy(genrebuf,c+7,size-1); */
576                     /* genrebuf[size-1]='\0'; */
577                     /* genre=atoi(&genrebuf[1]); */
578                     genre = atoi(c + 8);
579                 }
580                 c += size + 6;
581             }
582         if(version == 3 || version == 4)
583             while(c < buffer + size) {
584                 int size =
585                     (*(c + 4) << 24) + (*(c + 5) << 16) + (*(c + 6) << 8) +
586                     (*(c + 7));
587                 if(*c == 0)
588                     break;
589                 if(strncmp(c, "TIT2", 4) == 0) {
590                     strncpy(title, c + 11, size - 1);
591                     title[size - 1] = '\0';
592                 }
593                 if(strncmp(c, "TPE1", 4) == 0) {
594                     strncpy(artist, c + 11, size - 1);
595                     artist[size - 1] = '\0';
596                 }
597                 if(strncmp(c, "TCON", 4) == 0) {
598                     /* strncpy(genrebuf,c+11,size-1); */
599                     /* genrebuf[size-1]='\0'; */
600                     /* genre=atoi(&genrebuf[1]); */
601                     genre = atoi(c + 12);
602                 }
603                 c += size + 10;
604             }
605     }
606
607     while(c < buffer + lus - 10) {
608         if(*c == 0xFF && (*(c + 1) & 0xF0) == 0xF0) {
609             int version;
610             int lay;
611             int bitrate_index;
612             int bitrate;
613             version = 2 - (*(c + 1) >> 3 & 1);
614             lay = 4 - (*(c + 1) >> 1 & 3);
615             bitrate_index = *(c + 2) >> 4 & 0xF;
616             if(version >= 1 && version <= 2 && lay - 1 >= 0 && lay - 1 <= 2
617                && bitrate_index >= 0 && bitrate_index <= 14)
618                 bitrate = bitrates[version - 1][lay - 1][bitrate_index];
619             else
620                 bitrate = 0;
621             if(bitrate != 0) {
622                 fseek(fic, 0, SEEK_END);
623                 duration = (ftell(fic) + buffer - c) / 125 / bitrate;
624             } else
625                 duration = 0;
626             break;
627         }
628         c++;
629     }
630
631     /* try ID3v1 */
632     if(strlen(artist) == 0 && strlen(title) == 0) {
633         fseek(fic, -128, SEEK_END);
634         lus = fread(buffer, 1, 128, fic);
635         if(lus == 128 && buffer[0] == 'T' && buffer[1] == 'A'
636            && buffer[2] == 'G') {
637             strncpy(title, buffer + 3, 30);
638             title[30] = '\0';
639             c = title + 29;
640             while(c > title && *c == ' ')
641                 *(c--) = '\0';
642             strncpy(artist, buffer + 33, 30);
643             artist[30] = '\0';
644             c = artist + 29;
645             while(c > artist && *c == ' ')
646                 *(c--) = '\0';
647             /* strncpy(album,buffer+65,30); */
648             /* strncpy(year,buffer+97,4); */
649             /* strncpy(comment,buffer+101,30); */
650             /* strncpy(genrebuf,buffer+127,1); genre[1]=0; */
651             genre = buffer[127];
652         }
653     }
654
655     fclose(fic);
656 }
657
658 void parse_ogg(unsigned char *file)
659 {
660     FILE *fic;
661     unsigned char *c;
662     int lus;
663     int sample_rate;
664     int samples;
665
666     if(debug)
667         fprintf(stderr, "Debug >> parsing ogg : %s\n", file);
668
669     /* read header */
670     if((fic = fopen(file, "r")) == NULL) {
671         fprintf(stderr, "Warning >> can't open file : %s\n", file);
672         return;
673     }
674     lus = fread(buffer, 1, OGG_BASE, fic);
675
676     /* try Ogg */
677     if(buffer[0] != 'O' && buffer[1] != 'g' && buffer[2] != 'g') {
678         fprintf(stderr, "Warning >> not a Ogg header : %s\n", file);
679         return;
680     }
681
682     c = buffer + 0x28;
683     sample_rate =
684         (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
685
686     while(c < buffer + lus - 10) {
687         int size;
688         if(strncasecmp(c, "TITLE=", 6) == 0) {
689             size =
690                 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
691                 (*(c - 1) << 24);
692             strncpy(title, c + 6, size - 6);
693             title[size - 6] = '\0';
694             c += size;
695         }
696         if(strncasecmp(c, "ARTIST=", 7) == 0) {
697             size =
698                 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
699                 (*(c - 1) << 24);
700             strncpy(artist, c + 7, size - 7);
701             artist[size - 7] = '\0';
702             c += size;
703         }
704         if(strncasecmp(c, "GENRE=", 6) == 0) {
705             static int i = 0;
706             size =
707                 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
708                 (*(c - 1) << 24);
709             strncpy(genrebuf, c + 6, size - 6);
710             genrebuf[size - 6] = '\0';
711             c += size;
712             for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
713                 if(strcasecmp(ID3_v1_genre_description[i], genrebuf) == 0) {
714                     genre = i;
715                     break;
716                 }
717                 if(i == ID3_NR_OF_V1_GENRES)
718                     genre = 0;
719             }
720         }
721         c++;
722     }
723
724     fseek(fic, -OGG_BASE, SEEK_END);
725     lus = fread(buffer, 1, OGG_BASE, fic);
726     c = buffer + lus - 1;
727     while(strncmp(c, "OggS", 4) != 0 && c > buffer)
728         c--;
729     if(c != buffer) {
730         c += 6;
731         samples =
732             (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
733         duration = samples / sample_rate;
734     }
735
736     fclose(fic);
737 }
738
739
740 void parse_mpc(unsigned char *file)
741 {
742     FILE *fic;
743     unsigned char *c;
744     int lus;
745     int sample_rates[4] = { 44100, 48000, 37800, 32000 };
746     int frame_count;
747     int size, items;
748     int i;
749
750     if(debug)
751         fprintf(stderr, "Debug >> parsing mpc : %s\n", file);
752
753     /* read header */
754     if((fic = fopen(file, "r")) == NULL) {
755         fprintf(stderr, "Warning >> can't open file : %s\n", file);
756         return;
757     }
758     lus = fread(buffer, 1, 12, fic);
759
760     /* try Musepack */
761     if(buffer[0] != 'M' && buffer[1] != 'P' && buffer[2] != '+') {
762         fprintf(stderr, "Warning >> not a Musepack header : %s\n", file);
763         return;
764     }
765
766     /* only version 7 */
767     if(buffer[3] != 7) {
768         fprintf(stderr, "Warning >> only Musepack SV7 supported : %s\n",
769                 file);
770         return;
771     }
772
773     /* duration */
774     c = buffer + 4;
775     frame_count =
776         (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
777     c += 5;
778     duration = frame_count * 1152 / sample_rates[*c & 3];
779
780     /* try APETAGEX footer */
781     fseek(fic, -32, SEEK_END);
782     lus = fread(buffer, 1, 32, fic);
783     if(lus == 32 && strncmp(buffer, "APETAGEX", 8) == 0) {
784         c = buffer + 12;
785         size =
786             (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
787         size += 32;
788         c += 4;
789         items =
790             (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
791         fseek(fic, -size, SEEK_END);
792         lus = fread(buffer, 1, size, fic);
793         if(lus == size && strncmp(buffer, "APETAGEX", 8) == 0) {
794             c = buffer + 32;
795             while(items--) {
796                 size =
797                     (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) +
798                     (*(c + 3) << 24);
799                 c += 8;
800                 if(strcasecmp(c, "TITLE") == 0) {
801                     strncpy(title, c + 6, size);
802                     title[size] = '\0';
803                 }
804                 if(strcasecmp(c, "ARTIST") == 0) {
805                     strncpy(artist, c + 7, size);
806                     artist[size] = '\0';
807                 }
808                 if(strcasecmp(c, "GENRE") == 0) {
809                     for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
810                         strncpy(genrebuf, c + 6, size);
811                         genrebuf[size] = '\0';
812                         if(strcasecmp
813                            (ID3_v1_genre_description[i], genrebuf) == 0) {
814                             genre = i;
815                             break;
816                         }
817                         if(i == ID3_NR_OF_V1_GENRES)
818                             genre = 0;
819                     }
820                 }
821                 c += strlen(c) + 1 + size;
822             }
823         }
824     }
825
826     fclose(fic);
827 }
828
829 #define FSN  32
830 #define MAXINO  (1<<24)
831 #define INOTYP  unsigned long
832 #define regbit_qry(x,y)  ( x[( (y) / sizeof(INOTYP) )]  &  1<<( (y) % sizeof(INOTYP) ) )
833 #define regbit_set(x,y)  ( x[( (y) / sizeof(INOTYP) )]  |=  1<<( (y) % sizeof(INOTYP) ) )
834
835 int hlink_check(struct stat *info)
836 {
837     /*
838      * for speed this subroutine should only be called
839      * - if the file has more than one hardlink
840      * - if the file is a resolved softlink
841      */
842     /* the persistent variables */
843     static INOTYP *list[FSN];
844     static dev_t name[FSN];
845     /* some temporary variables */
846     int fsn, is_registered = 0;
847
848     /* assertions - in case parameters are lowered for less memory usage */
849     assert(fsn < FSN);
850     assert((info->st_ino) / sizeof(INOTYP) < MAXINO);
851
852     /* search which internal registration number is used for this filesystem */
853     for(fsn = 0; (name[fsn] != (info->st_dev)) && (name[fsn] != 0); fsn++);
854
855     /* if file system is not registered yet, do it and leave */
856     if(name[fsn] == 0) {
857         name[fsn] = (info->st_dev);
858         /* provide space for the bitmap that maps the inodes of this file system */
859         list[fsn] = (INOTYP *) calloc(MAXINO, sizeof(INOTYP));
860         /* no comparison is needed in empty lists ... return */
861         if(debug)
862             fprintf(stderr,
863                     "Debug >> Linked >> Init List %04x @mem %04lx\n",
864                     (int)name[fsn], (long)&list[fsn]);
865     } else {
866         /* this looks more complicated than it really is */
867         /* the idea is very simple: 
868          *  provide a bitmap that maps all inodes of a file system
869          *  to mark all files that have already been visited.
870          *  If it is already visited, do not add it to the playlist
871          */
872         /*
873          * The difficulty is as follows:
874          *   struct inode_bitmap { char registered:1; } bitmap[1<<MAXINO];
875          * would be byte-aligned and would allocate at least eight times the needed space.
876          * Feel free to change the definitions that are involved here, if you know better.
877          */
878         if(regbit_qry(list[fsn], (info->st_ino)))
879             is_registered = 1;
880         else
881             regbit_set(list[fsn], (info->st_ino));
882         /*
883          * the debug expression is more complicated then the working stuff
884          */
885         if(debug)
886             fprintf(stderr, "Debug >> Linked >> DEV %04x INO %06x => "
887                     "list[%02x][%04x] = %04x & %04x --> %s registered\n",
888                     (int)info->st_dev, (int)info->st_ino, fsn,
889                     (int)((info->st_ino) / sizeof(INOTYP)),
890                     (int)list[fsn][(info->st_ino) / sizeof(INOTYP)],
891                     1 << ((info->st_ino) % sizeof(INOTYP)),
892                     is_registered ? "Already" : "Not");
893     }
894     return is_registered;
895 }
896
897
898 void parse_file(unsigned char *newpath)
899 {
900     unsigned char ext[5];
901     int j, encoding = 0;
902
903     for(j = 0; j < 5; j++)
904         ext[j] = tolower(newpath[strlen(newpath) - 4 + j]);
905     artist[0] = '\0';
906     title[0] = '\0';
907     duration = -2;
908     if(strcmp(".mp2", ext) == 0) {
909         duration = -1;
910         parse_mp3(newpath);
911         encoding = MP2ENC;
912     }
913     if(strcmp(".mp3", ext) == 0) {
914         duration = -1;
915         parse_mp3(newpath);
916         encoding = MP3ENC;
917     }
918     if(strcmp(".mpc", ext) == 0) {
919         duration = -1;
920         parse_mpc(newpath);
921         encoding = MPCENC;
922     }
923     if(strcmp(".mp+", ext) == 0) {
924         duration = -1;
925         parse_mpc(newpath);
926         encoding = MPPENC;
927     }
928     if(strcmp(".ogg", ext) == 0) {
929         duration = -1;
930         parse_ogg(newpath);
931         encoding = OGGENC;
932     }
933     if(strcmp(".wav", ext) == 0) {
934         duration = -1;          /* parse_wav(newpath); */
935         encoding = WAVENC;
936     }
937     /* faketitle() */
938     if((strlen(artist) == 0) && (strlen(title) == 0)) {
939         // there are no tag infos read
940         // use file name to state substitute it
941         char *c = strrchr(newpath, separator);
942         if (c == NULL) c = newpath;
943         strcpy(artist, ++c);
944         // arbitrarily use the first '-'
945         // to separate artist and title
946         c = strchr(artist, '-');
947         if(c != NULL) {         // if trenner found, divide file name 
948             *c = '\0';
949             strcpy(title, ++c);
950             c = strrchr(title, '.');
951             if(c != NULL)
952                 *c = '\0';
953         } else {                // no trenner found, assume
954             // no artist, only title
955             strcpy(title, artist);
956             artist[0] = '\0';
957         }
958         // replace underscores by spaces
959         for(c = artist; (c = strchr(c, '_')) != NULL; c++)
960             *c = ' ';
961         for(c = title; (c = strchr(c, '_')) != NULL; c++)
962             *c = ' ';
963     }
964     /* faketitle() end */
965
966     if(duration != -2 && genrelist[genre]) {    /* is it an audio file ? */
967         counter++;
968         switch (format) {
969         case 0:
970             if(duration != -1) {
971                 printf("#EXTINF:%d,%s - %s%s", duration, artist, title,
972                        eol);
973             }
974             print_path(newpath);
975             printf("%s", eol);
976             break;
977         case 1:
978             printf("File%d=", counter);
979             print_path(newpath);
980             printf("%sTitle%d=%s - %s%s", eol, counter, artist, title,
981                    eol);
982             if(duration != -1)
983                 printf("Length%d=%d%s", counter, duration, eol);
984             break;
985         case 2:
986             printf("<tr><td>%d</td><td>%s</td><td>%s</td><td>", counter,
987                    artist, title);
988             if(duration == -1)
989                 printf("?</td></tr>%s", eol);
990             else
991                 printf("%d:%s%d</td></tr>%s", duration / 60,
992                        duration % 60 < 10 ? "0" : "", duration % 60, eol);
993             break;
994         case 3:
995             if(duration != -1) {
996                 struct stat infos;
997                 char timebuffer[256];
998
999                 if(stat(newpath, &infos) != 0) {
1000                     fprintf(stderr, "Warning >> can't stat entry : %s\n",
1001                             newpath);
1002                     return;
1003                 }
1004                 strftime(timebuffer, 255, "%a %d %b %Y %T %z", localtime(&(infos.st_mtime)));   /* ctime() had a trailing CR */
1005                 printf("\t<item>%s", eol);
1006                 printf("\t\t<author>");
1007                 myputstr(artist);
1008                 printf("</author>%s\t\t<title>", eol);
1009                 myputstr(title);
1010                 printf("</title>%s", eol);
1011
1012                 if(referal == NULL) {
1013                     noreferal(newpath, artist, title);
1014                 } else
1015                     reference(newpath);
1016                 printf("\t\t<pubDate>%s</pubDate>%s\t\t<enclosure url=\"",
1017                        timebuffer, eol);
1018                 print_webpath(newpath);
1019                 printf("\" length=\"%d\" type=\"%s\"/>%s\t\t<guid>",
1020                        (int)infos.st_size, magic[encoding], eol);
1021                 print_pathtail(newpath);
1022                 printf("</guid>%s", eol);
1023                 if(duration > 3599)
1024                     printf
1025                         ("\t\t<itunes:duration>%d:%02d:%02d</itunes:duration>%s",
1026                          duration / 3600, (duration / 60) % 60,
1027                          duration % 60, eol);
1028                 else
1029                     printf
1030                         ("\t\t<itunes:duration>%d:%02d</itunes:duration>%s",
1031                          duration / 60, duration % 60, eol);
1032                 if(strlen(artist) != 0) {
1033                     printf("\t\t<itunes:author>");
1034                     myputstr(artist);
1035                     printf("</itunes:author>%s", eol);
1036                 }
1037                 printf("\t</item>%s", eol);
1038             }
1039             break;
1040         case 4: // printing output for Sansa players
1041             myplaputstr("HARP, ");
1042             myplaputstr(newpath);
1043             myplaputstr(eol);
1044             break;
1045         }
1046     }
1047 }
1048
1049 void parse_directory(unsigned char *path)
1050 {
1051     int i, n;
1052     struct dirent **namelist;
1053     unsigned char newpath[PATH_MAX];
1054     struct stat infos;
1055
1056     if(debug)
1057         fprintf(stderr, "Debug >> parsing directory : %s\n", path);
1058     if(stat(path, &infos) != 0) {
1059         fprintf(stderr, "Warning >> can't stat entry : %s\n", path);
1060         return;
1061     }
1062     /* check if it is a filename */
1063     if(S_ISREG(infos.st_mode) || S_ISLNK(infos.st_mode)) {
1064         parse_file(path);
1065         return;
1066     }
1067     /* must be a directory - or something unusable like pipe, socket, etc */
1068     if((n = scandir(path, &namelist, 0, alphasort)) < 0) {
1069         fprintf(stderr, "Warning >> can't open directory : %s\n", path);
1070         return;
1071     }
1072     for(i = 0; i < n; i++) {
1073         sprintf(newpath, "%s/%s", path, namelist[i]->d_name);
1074
1075         if(stat(newpath, &infos) != 0) {
1076             fprintf(stderr, "Warning >> can't stat entry : %s\n", newpath);
1077             continue;
1078         }
1079         if(recursive && S_ISDIR(infos.st_mode)
1080            && strcmp(namelist[i]->d_name, ".") != 0
1081            && strcmp(namelist[i]->d_name, "..") != 0)
1082             parse_directory(newpath);
1083         /* hlink_check() might be applied more selective ... avoidhlink is only a simple prereq */
1084         if(S_ISREG(infos.st_mode)
1085            && !(avoidhlinked && hlink_check(&infos))) {
1086             parse_file(newpath);
1087         }
1088         free(namelist[i]);
1089     }
1090     free(namelist);
1091 }
1092
1093 int main(int argc, char **argv)
1094 {
1095     winorunix = one2one;
1096     basemap = one2one;
1097     parse_options(argc, argv);
1098     if(optind == argc)
1099         usage();
1100     switch (format) {
1101     case 0:
1102         printf("#EXTM3U%s", eol);
1103         break;
1104     case 1:
1105         printf("[playlist]%s", eol);
1106         break;
1107     case 2:
1108         printf
1109             ("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">%s%s<html>%s%s<head>%s<title>Playlist generated by FAPG "
1110              VERSION
1111              "</title>%s<meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\" />%s<style type=\"text/css\">%s<!--%s%sbody,td,tr {%s font-family: Verdana, Arial, Helvetica, sans-serif;%s  font-size: 12px;%s  color: #000000;%s}%s%sbody {%s  background: #ffffff;%s}%s%sth {%s  text-align: center;%s  background: #ffcccc;%s  padding-left: 15px;%s  padding-right: 15px;%s  border: 1px #dd8888 solid;%s}%s%std {%s  text-align: center;%s  background: #eeeeee;%s  padding-left: 15px;%s  padding-right: 15px;%s  border: 1px #cccccc solid;%s}%s%sh1 {%s  font-size: 25px;%s}%s%sp {%s  font-size: 10px;%s}%s%sa {%s  color: #993333;%s  text-decoration: none;%s}%s%sa:hover {%s text-decoration: underline;%s}%s%s-->%s</style>%s</head>%s%s<body>%s%s<h1>Playlist</h1>%s%s<table>%s<tr><th>Entry</th><th>Artist</th><th>Title</th><th>Length</th></tr>%s",
1112              eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1113              eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1114              eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1115              eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1116              eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1117              eol, eol, eol);
1118         break;
1119     case 3:
1120         {
1121             time_t zeit;
1122             char timebuffer[256];
1123             time(&zeit);
1124             strftime(timebuffer, 255, "%a %d %b %Y %T %z",
1125                      localtime(&zeit));
1126             printf
1127                 ("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>%s<!-- generator=\"FAPG "
1128                  VERSION
1129                  " -->%s<rss xmlns:itunes=\"http://www.itunes.com/DTDs/Podcast-1.0.dtd\" version=\"2.0\">%s    <channel>%s\t<title>%s - %s - %s</title>%s\t<description>Directory Tree %s</description>%s\t<link>%s</link>%s\t<itunes:image href=\"%s/xml/podcast.jpg\"/>%s\t<lastBuildDate>%s</lastBuildDate>%s\t<generator>FAPG "
1130                  VERSION
1131                  "</generator>%s\t<image>%s\t\t<url>%s/podcast.jpg</url>%s\t\t<title>Server Logo</title>%s\t\t<link>%s</link>%s\t\t<description>Feed provided by FAPG. Click to visit.</description>%s\t</image>%s\t<itunes:owner>%s\t\t<itunes:name>Admin %s</itunes:name>%s\t\t<itunes:email>podcast@%s</itunes:email>%s\t</itunes:owner>%s\t<category>Various</category>%s\t<itunes:subtitle>Directory Tree %s</itunes:subtitle>%s\t<itunes:author>%s</itunes:author>%s\t<copyright>unknown</copyright>%s\t<language>%s</language>%s\t<itunes:explicit>No</itunes:explicit>%s\t<ttl>1800</ttl>%s",
1132                  eol, eol, eol, eol, hostname, dir, argv[optind], eol,
1133                  prefix, eol, base, eol, prefix, eol, timebuffer, eol, eol,
1134                  eol, base, eol, eol, base, eol, eol, eol, eol, base, eol,
1135                  hostname, eol, eol, eol, dir, eol, getenv("LOGNAME"), eol,
1136                  eol, getenv("LANG"), eol, eol, eol);
1137             unix2dos[38] = 43;  // I never made an rss feed work with '&' in it
1138             basemap = noand;
1139         }
1140         break;
1141     case 4: 
1142         {
1143             eol = "\r\n";
1144             myplaputstr( "PLP PLAYLIST\r\nVERSION 1.20\r\n\r\n" );
1145         }
1146     }
1147     for(; optind < argc; optind++) {
1148         parse_directory(argv[optind]);
1149     }
1150     switch (format) {
1151     case 1:
1152         printf("NumberOfEntries=%d%sVersion=2%s", counter, eol, eol);
1153         break;
1154     case 2:
1155         printf
1156             ("</table>%s%s<p>Playlist generated by <a href=\"http://royale.zerezo.com/fapg/\">FAPG "
1157              VERSION "</a></p>%s%s</body>%s%s</html>", eol, eol, eol, eol,
1158              eol, eol);
1159         break;
1160     case 3:
1161         printf("    </channel>%s</rss>%s", eol, eol);
1162         break;
1163     }
1164     if(genrelist)
1165         free(genrelist);
1166     exit(0);
1167 }